把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF878E】Numbers on the blackboard(贪心)

题目链接

  • 给定一个长度为 \(n\) 的序列 \(a_{1\sim n}\)
  • \(q\) 次询问,每次取出序列中的一个区间 \([l,r]\) 内的元素进行操作。
  • 一次操作可以合并相邻两个元素(假设依次为 \(x\)\(y\)),得到一个值为 \(x+2y\) 的新元素。对于每次询问,求出求最终能得到的元素的最大值。
  • \(1\le n,q\le10^5\)\(|a_i|\le10^9\),答案向 \(10^9+7\) 取模

巧妙的贪心策略

设第 \(i\) 个元素对答案的贡献系数为 \(2^{k_i}\)

除开头元素外,第 \(i\) 个元素都至少被合并一次,且一次合并之后就会始终跟随着第 \(i-1\) 个元素,也就是说 \(1\le k_i\le k_{i-1}+1\)

而我们肯定想让正数的系数尽可能大,因此如果一个数是正数,我们肯定贪心地让 \(k_i\) 取满 \(k_{i-1}+1\)

考虑把 \(k_i\) 连续递增的一段元素称作一个块。

则正数肯定会被添到上一个块的末尾,负数则会自成一个新块。

但要,如果正数被添到上一个块末尾后上一个块的权值变成了正数,我们需要继续把这个块看成一个元素向前合并。具体实现可以用一个栈来维护所有的块,要保证除第一个块之外的所有块权值为负。

注意用于判断正负的权值是不能取模的,而如果无脑维护可能会爆 long long。实际上,当权值非常大时,肯定能一路向前合并完,可以统一设为 INF。

离线询问

询问可以离线,按右端点 \(r\) 排序,这样我们就可以维护好加入 \(1\sim r\) 中所有元素的局面,然后每次 lower_bound 找出第 \(l\) 个元素所在的块(假设为 \(x\))。

\(l\) 到所在块结尾 \(ed\) 中第 \(i\) 个元素贡献系数为 \(2^{i-l}\),只需用 \(\sum_{i=l}^{ed}a_i\times 2^i\) 除以 \(2^l\),这可以预处理一个前缀和差分求解。

而第 \(x+1\) 个块开始的答案就是这些块的权值和,同样维护前 \(i\) 个块的权值和每次差分一下即可。

注意除开头元素外,每个元素至少合并一次,\(k_i\ge 1\)

代码:\(O(n\log n)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
#define X 1000000007
#define I2 500000004
#define LL long long
#define INF (LL)1e18
using namespace std;
int n,a[N+5],ans[N+5];struct Q {int p,l,r;I bool operator < (Cn Q& o) Cn {return r<o.r;}}q[N+5];
int pw[N+5],ipw[N+5],S[N+5],V[N+5],St[N+5],F[N+5];LL G[N+5];
namespace FastIO
{
	#define FS 100000
	#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
	#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
	int ff,OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
	I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
	Tp I void read(Ty& x) {x=0,ff=1;W(!isdigit(oc=tc())) ff=oc^'-'?1:-1;W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));x*=ff;}
	Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
	Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int main()
{
	RI Qt,i;for(read(n,Qt),i=1;i<=n;++i) read(a[i]);for(i=1;i<=Qt;++i) read(q[i].l,q[i].r),q[i].p=i;
	for(pw[0]=ipw[0]=i=1;i<=n;++i) pw[i]=2LL*pw[i-1]%X,ipw[i]=1LL*I2*ipw[i-1]%X,S[i]=(S[i-1]+1LL*pw[i]*(a[i]+X))%X;//预处理
	RI x,j=1,T=0;for(sort(q+1,q+Qt+1),i=1;i<=n;++i)//将询问离线,枚举右端点
	{
		St[++T]=i,F[T]=(a[i]+X)%X,G[T]=a[i];//新开一个块
		W(T>1&&G[T]>=0) x=St[T]-St[T-1],F[T-1]=(F[T-1]+1LL*F[T]*pw[x])%X,G[T-1]=G[T-1]+1.0*G[T]*pw[x]>=INF?INF:G[T-1]+G[T]*pw[x],--T;//若最后一个块权值非负,向前合并
		V[T]=(V[T-1]+F[T])%X,St[T+1]=i+1;//V记录前i块元素之和
		W(j<=Qt&&q[j].r==i) x=upper_bound(St+1,St+T+1,q[j].l)-St,ans[q[j].p]=(1LL*ipw[q[j].l]*(S[St[x]-1]-S[q[j].l-1]+X)+2LL*(V[T]-V[x-1]+X))%X,++j;//找到l所在块求解
	}
	for(i=1;i<=Qt;++i) writeln(ans[i]);return clear(),0;
}
posted @ 2021-09-08 21:22  TheLostWeak  阅读(53)  评论(0编辑  收藏  举报