【CF878E】Numbers on the blackboard
题意:给你一个长度为n个数列,你每次可以进行如下操作:
选取两个相邻的数x,y(x在y左面),然后将这两个数去掉,用x+2y替换它。
重复此操作直到序列中只有一个数为止。你可以任意决定每次合并哪两个数,求最后得到的数的最大值。
为了加大难度,现有q次询问,每次询问给出l,r,问你对[l,r]这段区间进行操作能得到的最大值是什么。
n,q<=100000,ai<=10^9
题解:先不考虑l,r的限制,整个操作可以看成:让你最大化$\sum a_i\times 2^{k_i},k_0=0,1<=k_i<=k_{i-1}+1$。我们从左往右逐个加入每个数,如果ai是负数,我们直接令$k_i=1$;否则我们令$k_i=k_{i-1}+1$。这样的话最终得到的k一定是分为若干段,每段(除了第一段)都是开头的k=1,然后k不断++。我们还需要判断:在加入ai后,如果最后一段合并之后的和变成了正数,那么还要将最后一段整体向前合并,直到和为负数为止。
如果考虑l,r呢?我们可以离线,对于r=i,我们用并查集找到l所在的块,然后统计一下答案即可。
在判断一个块内合并后总和是否是正数时要讨论一下。
#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <utility> using namespace std; #define mp(A,B) make_pair(A,B) typedef long long ll; const ll P=1000000007; const ll inv=500000004; const int maxn=100010; int n,m; int f[maxn],pre[maxn]; ll v[maxn],s[maxn],sum[maxn],ans[maxn],pw[maxn],sp[maxn]; vector<pair<int,int> > q[maxn]; vector<pair<int,int> >::iterator it; int find(int x) { return (f[x]==x)?x:(f[x]=find(f[x])); } inline void merge(int a,int b) { if((a-pre[a]>31&&sum[b]>0)||sum[a]+(sum[b]<<(a-pre[a]))>P) sum[b]=P; else sum[b]=sum[a]+(sum[b]<<(a-pre[a])); f[a]=f[b],pre[b]=pre[a]; } inline ll query(int a,int b) { return (s[a]-s[b+1]*pw[b-a+1]%P+P)%P; } inline int rd() { int ret=0,f=1; char gc=getchar(); while(gc<‘0‘||gc>‘9‘) {if(gc==‘-‘) f=-f; gc=getchar();} while(gc>=‘0‘&&gc<=‘9‘) ret=ret*10+(gc^‘0‘),gc=getchar(); return ret*f; } int main() { n=rd(),m=rd(); int i,a,b; for(pw[0]=i=1;i<=n;i++) f[i]=i,pre[i]=i-1,v[i]=rd(),pw[i]=(pw[i-1]<<1)%P; for(i=n;i>=1;i--) s[i]=((s[i+1]<<1)+v[i]+P)%P; for(i=1;i<=m;i++) a=rd(),b=rd(),q[b].push_back(mp(a,i)); for(i=1;i<=n;i++) { sum[i]=v[i]; while(pre[i]&&sum[i]>=0) merge(pre[i],i); sp[i]=(sp[pre[i]]+(query(pre[i]+1,i)<<1))%P; for(it=q[i].begin();it!=q[i].end();it++) { a=(*it).first,b=find(a); ans[(*it).second]=(sp[i]-sp[b]+query(a,b)+P)%P; } } for(i=1;i<=m;i++) printf("%I64d\n",ans[i]); return 0; }