bzoj4540: [Hnoi2016]序列
传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4540
思路:又是莫队....
我们发现左右端点移动时,只会增加或删除某个点开头或结尾的区间
先考虑右端点从r移动到r+1
令p为[l,r]中最小值的位置
那么它会对新加的区间中的p-l+1个区间产生a[p]的贡献
另一些左端点在[p+1,r],右端点是r+1的区间怎么统计呢?
首先用单调栈求出l[i],r[i]表示向左第一个小于a[i]的数的位置,向右第一个小于等于a[i]的数的位置
(注意:一个是小于,一个是小于等于,这样是防止重复计算)
预处理出两个类似前缀和的数组sl[i]和sr[i]
其中sl[i]=sl[l[i]]+(i-l[i])*a[i],sr同理
表示以这个为结束的答案的前缀和
左端点在l[i]及以前的答案就是sl[l[i]],左端点在[l[i],i]之间的最小值肯定是a[i],所以答案加上(i-l[i])*a[i]
那么左端点在[p+1,r],右端点是r+1的区间对答案的贡献就是sl[r+1]-sl[p]
那么移动后,答案加上(p-l+1)*a[i]+sl[r]-sl[p]即可
左端点移动及删除类似
#include<cmath> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> typedef long long ll; const int maxn=100010,maxk=22,inf=1e9; using namespace std; int n,Q,sz,top,a[maxn],st[maxn][maxk],pw[maxk],stk[maxn],L[maxn],R[maxn],bel[maxn],lg[maxn];ll sl[maxn],sr[maxn],now,ans[maxn]; struct quer{int l,r,id;}q[maxn]; bool operator <(quer a,quer b){return bel[a.l]==bel[b.l]?a.r<b.r:bel[a.l]<bel[b.l];} int getmin(int x,int y){return a[x]<a[y]?x:y;} int calc(int x,int y){ if (x>y) swap(x,y); int l=lg[y-x+1]; //printf("x=%d y=%d log=%d minpos%d\n",x,y,l,getmin(st[x][l],st[y-pw[l]+1][l])); return getmin(st[x][l],st[y-pw[l]+1][l]); } void modifyl(int l,int r,int op){ int p=calc(l,r); ll tmp=1ll*a[p]*(r-p+1)+sr[l]-sr[p]; now+=op*tmp; } void modifyr(int l,int r,int op){ int p=calc(l,r); ll tmp=1ll*a[p]*(p-l+1)+sl[r]-sl[p]; now+=op*tmp; } void work(){ sort(q+1,q+1+Q);now=a[1]; for (int l=1,r=1,i=1;i<=Q;i++){ //printf("i=%d\n",i); for (;r<q[i].r;r++) modifyr(l,r+1,1); for (;l>q[i].l;l--) modifyl(l-1,r,1); for (;r>q[i].r;r--) modifyr(l,r,-1); for (;l<q[i].l;l++) modifyl(l,r,-1); ans[q[i].id]=now; } for (int i=1;i<=Q;i++) printf("%lld\n",ans[i]); } int main(){ //freopen("sequence1.in","r",stdin);freopen("sequence.out","w",stdout); scanf("%d%d",&n,&Q),sz=sqrt(n),lg[0]=-1; pw[0]=1;for (int i=1;i<=18;i++) pw[i]=pw[i-1]<<1; for (int i=1;i<=n;i++) lg[i]=lg[i>>1]+1,bel[i]=(i-1)/sz+1; for (int i=1;i<=n;i++) scanf("%d",&a[i]); for (int i=1;i<=n;i++) st[i][0]=i; for (int j=1;j<=18;j++) for (int i=1;i<=n;i++){ st[i][j]=st[i][j-1]; if (i+pw[j-1]<=n) st[i][j]=getmin(st[i][j-1],st[i+pw[j-1]][j-1]); //printf("%d ",st[i][j]); } for (int i=1;i<=n;i++){ while (top&&a[stk[top]]>=a[i]) R[stk[top--]]=i; stk[++top]=i; } while (top) R[stk[top--]]=n+1; for (int i=n;i;i--){ while (top&&a[stk[top]]>a[i]) L[stk[top--]]=i; stk[++top]=i; } while (top) L[stk[top--]]=0; //for (int i=1;i<=n;i++) printf("i=%d L=%d R=%d\n",i,L[i],R[i]); for (int i=n;i;i--) sr[i]=sr[R[i]]+1ll*(R[i]-i)*a[i]; for (int i=1;i<=n;i++) sl[i]=sl[L[i]]+1ll*(i-L[i])*a[i]; //for (int i=1;i<=n;i++) printf("i=%d sl=%lld sr=%lld\n",i,sl[i],sr[i]); for (int i=1;i<=Q;i++) scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i; work(); return 0; } /* 5 5 5 2 4 1 3 1 5 1 3 2 4 3 5 2 5 */