csp模拟 生与死的境界【贪心】【带权并查集】

传送门

合并使得x,y变成一个x+2y的数,求最后剩一个数的最大值。

每次询问针对一个区间。

首先发现,这个数列每合并一次,都会对从第二个数开始的所有数,依次多产生2^k系数的贡献,k依次增加。

也就是说,合并两个快,对左边的块没有影响。

那么我们从最后开始考虑,如果当前块是正数,则和前面块合并,会增大贡献。反之不会。

按照这个方式进行到最后得到的块序列除了第一块可能为正,其它块和都是负数,否则可以继续合并。

然后我们发现可以在末端添加一个数字,只要变成正的就往前合并即可。

将查询按照r排序,如果对应r,则将l及之前所在的块踢掉,然后强行加上l到所在快右端点的贡献以及这一段的贡献。带权并查集。

这个l到右端点的后缀不会影响合并,否则后面的块早就合并上来了。

那么维护哈希值,直接计算即可。注意带权并查集维护权值的时候,超过1e9就可以直接取min,因为负数块显然不会有贡献小于-1e9的。因为我不会合并两个负数块到一起。

维护每一块的左端点,右端点。

然后查询。

#include<bits/stdc++.h>
using namespace std;
#define in read()
#define int long long
int in{
	int cnt=0,f=1;char ch=0;
	while(!isdigit(ch)){
		ch=getchar();if(ch=='-')f=-1;
	}
	while(isdigit(ch)){
		cnt=cnt*10+ch-48;
		ch=getchar();
	}return cnt*f;
}
struct node{
	int l,r,id;
}que[500003];
int fa[500003],sum[500003];
int n,q,a[500003];
bool cm(node a,node b){
	return a.r<b.r;
}int ans[500003];
int jz[500003];
int pre[500003];
int S[500003];
int suf[500003];
const int mod=1e9+7;
void merge(int x,int y){
	fa[x]=y;pre[y]=pre[x];
	int len=x-pre[x];
	if(len>30&&sum[y]>0||sum[x]+(sum[y]<<len)>mod)sum[y]=mod;
	else sum[y]=sum[x]+(sum[y]<<len);
}
int query(int x,int y){
	return (suf[x]-suf[y+1]*jz[y-x+1]%mod+mod)%mod;
}
int get(int x){
	if(fa[x])return fa[x]=get(fa[x]);return x;
}
signed main(){
	n=in;q=in;
	for(int i=1;i<=n;i++)a[i]=in;
	for(int i=1;i<=q;i++){
		que[i].l=in;que[i].r=in;que[i].id=i;
	}
	sort(que+1,que+q+1,cm);jz[0]=1;
	for(int i=1;i<=n;i++){fa[i]=0;pre[i]=i-1;sum[i]=a[i];jz[i]=(jz[i-1]<<1)%mod;}
	for(int i=n;i>=1;i--)suf[i]=((suf[i+1]<<1)%mod+a[i]+mod)%mod;
//	for(int i=1;i<=q;i++)cout<<que[i].r<<" ";cout<<endl;
//	for(int i=1;i<=n;i++)cout<<pre[i]<<" ";cout<<endl;
//	for(int i=1;i<=n;i++)cout<<sum[i]<<" ";cout<<endl;
//	for(int i=1;i<=n;i++)cout<<suf[i]<<" ";cout<<endl;
//	for(int i=1;i<=n;i++)cout<<jz[i]<<" ";cout<<endl;
	int pos=0;
	for(int i=1;i<=n;i++){
		while(pre[i]&&sum[i]>=0)merge(pre[i],i);
		S[i]=(S[pre[i]]+(query(pre[i]+1,i)<<1)%mod)%mod;
//		cout<<query(pre[i]+1,i)<<endl;
		while(que[pos+1].r==i){
			int x=get(que[pos+1].l);++pos;
			ans[que[pos].id]=(S[i]-S[x]+query(que[pos].l,x)+mod)%mod;
		}
	}
	for(int i=1;i<=q;i++)cout<<ans[i]<<'\n';
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值