【CF878E】Numbers on the blackboard 并查集

【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;
}

 

转载于:https://www.cnblogs.com/CQzhangyu/p/8098246.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值