題目傳送門:http://www.lydsy.com/JudgeOnline/problem.php?id=4826
題目分析:這題是我在今年4月份省賽前看到的,那個時候想了一下,發現不會做。7個月後的今天,我試圖把這個坑填了,然而想了很久還是不會做,最後只好%了一波網上的題解,才發現自己智商低下,思維僵化QAQ。
本題的主要思路是轉化貢獻。由於k是1~n的一個排列,所以如果L+1 < R,(L,R)中必定有唯一一個最大值。我們不妨枚舉這個最大值的位置i,然後找到i左邊和右邊第一個比i大的地方Left[i],Right[i]。那麼當L屬於[Left[i],i),R屬於(i,Right[i]]的時候,i就是區間(L,R)的最大值。根據題意,點對(Left[i],Right[i])的攻擊力為p1,點對(x,Right[i])(Left[i]+1<=x<=i-1)和(Left[i],x)(i+1<=x<=Right[i]-1)的攻擊力為p2。其餘的點對要麼中間的最大值不在i處,要麼無攻擊力。
如果以L為x軸,R為y軸,就可以將上述的攻擊力的計算轉化為平面內兩條線段和一個點的加法。由於每一個合法點對都會被計算僅一次貢獻(點對(i,i+1)除外,由於它中間沒有數,要特殊處理),所以攻擊力和平面上的點,線段一一對應。而詢問區間[a,b]的攻擊力,就相當於詢問這個平面內的一個正方形的和。於是離線,對詢問差分,再橫豎掃一遍這個平面,維護一個區間加法的線段樹即可:
這題我一開始沒有想到將攻擊力轉化成二維平面上的點和線段,然後想著維護一個單調棧,求出R為某個值的時候,哪些L有貢獻,然後就GG了,發現根本維護不了。看了題解之後我還想著(L,R)中有多個最大值怎麼辦,最後忽然意識到這是個排列……
(好像網上很多dalao都用了可持久化線段樹,估計是懶得離線吧qaq)
CODE:
#include<iostream>#include<string>#include<cstring>#include<cmath>#include<cstdio>#include<cstdlib>#include<stdio.h>#include<algorithm>using namespace std;const int maxn=200100;typedef long long LL;struct Tnode{ LL lazy,sum;} tree[maxn<<2];struct data{ int id,l,r,cnt;} ;data X[maxn<<1];data Y[maxn];int numx=0,numy=0;struct A{ int val,num,p;} ask[maxn<<1];LL ans[maxn];int cur=0;int sak[maxn];int tail;int Left[maxn];int Right[maxn];int a[maxn];int aL[maxn];int aR[maxn];int n,m,p1,p2;void Preparation(){ tail=0; sak[0]=0; for (int i=1; i<=n; i++) { while ( tail && a[ sak[tail] ]<=a[i] ) tail--; Left[i]=sak[tail]; sak[++tail]=i; } tail=0; sak[0]=n+1; for (int i=n; i>=1; i--) { while ( tail && a[ sak[tail] ]<=a[i] ) tail--; Right[i]=sak[tail]; sak[++tail]=i; } for (int i=1; i<=n; i++) { if ( Left[i] && Right[i]<=n ) numx++,X[numx].id=Left[i],X[numx].l=X[numx].r=Right[i],X[numx].cnt=p1; if ( Left[i] && Right[i]>i+1 ) numx++,X[numx].id=Left[i],X[numx].l=i+1,X[numx].r=Right[i]-1,X[numx].cnt=p2; if ( Right[i]<=n && Left[i]<i-1 ) numy++,Y[numy].id=Right[i],Y[numy].l=Left[i]+1,Y[numy].r=i-1,Y[numy].cnt=p2; }}bool Comp1(A x,A y){ return x.val<y.val;}bool Comp2(data x,data y){ return x.id<y.id;}void Down(int root,int L,int R){ if (tree[root].lazy) { int Le=root<<1; int Ri=Le|1; int mid=(L+R)>>1; LL &v=tree[root].lazy; tree[Le].lazy+=v; tree[Le].sum+=( v*(long long)(mid-L+1) ); tree[Ri].lazy+=v; tree[Ri].sum+=( v*(long long)(R-mid) ); v=0; }}void Update(int root,int L,int R,int x,int y,LL v){ if ( y<L || R<x ) return; if ( x<=L && R<=y ) { tree[root].lazy+=v; tree[root].sum+=( v*(long long)(R-L+1) ); return; } Down(root,L,R); int Le=root<<1; int Ri=Le|1; int mid=(L+R)>>1; Update(Le,L,mid,x,y,v); Update(Ri,mid+1,R,x,y,v); tree[root].sum=tree[Le].sum+tree[Ri].sum;}LL Query(int root,int L,int R,int x,int y){ if ( y<L || R<x ) return 0; if ( x<=L && R<=y ) return tree[root].sum; Down(root,L,R); int Le=root<<1; int Ri=Le|1; int mid=(L+R)>>1; LL vL=Query(Le,L,mid,x,y); LL vR=Query(Ri,mid+1,R,x,y); return (vL+vR);}int main(){ freopen("4826.in","r",stdin); freopen("4826.out","w",stdout); scanf("%d%d%d%d",&n,&m,&p1,&p2); for (int i=1; i<=n; i++) scanf("%d",&a[i]); Preparation(); for (int i=1; i<=m; i++) { int Le,Ri; scanf("%d%d",&Le,&Ri); ans[i]=p1*(Ri-Le); aL[i]=Le; aR[i]=Ri; cur++; ask[cur].num=i; ask[cur].val=Le-1; ask[cur].p=-1; cur++; ask[cur].num=i; ask[cur].val=Ri; ask[cur].p=1; } sort(ask+1,ask+cur+1,Comp1); sort(X+1,X+numx+1,Comp2); int t1=1,t2=1; while ( !ask[t1].val && t1<=cur ) t1++; for (int i=1; i<=n; i++) { while ( X[t2].id<=i && t2<=numx ) Update(1,1,n,X[t2].l,X[t2].r,X[t2].cnt),t2++; while ( ask[t1].val<=i && t1<=cur ) { int v=ask[t1].num; ans[v]+=( (long long)ask[t1].p*Query(1,1,n,aL[v],aR[v]) ); t1++; } } for (int i=1; i<=(n<<2); i++) tree[i].lazy=tree[i].sum=0; sort(Y+1,Y+numy+1,Comp2); t1=1,t2=1; while ( !ask[t1].val && t1<=cur ) t1++; for (int i=1; i<=n; i++) { while ( Y[t2].id<=i && t2<=numy ) Update(1,1,n,Y[t2].l,Y[t2].r,Y[t2].cnt),t2++; while ( ask[t1].val<=i && t1<=cur ) { int v=ask[t1].num; ans[v]+=( (long long)ask[t1].p*Query(1,1,n,aL[v],aR[v]) ); t1++; } } for (int i=1; i<=m; i++) printf("%lld\n",ans[i]); return 0;}