jzoj6374. 【NOIP2019模拟2019.10.04】结界[生与死的境界]

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

赛时

花了近2h来课这道题.
发现了贪心规律,本想着可以水水70分。
然鹅发现数据过大,似乎要打高精度?
心态崩了,最后连20分都没拿到。
后来才发现一个小小的性质,不用打高精度。
太菜了。

题解

首先我们画画柿子。
答案即为 ∑ i = l r a i ∗ 2 k i \sum_{i=l}^r a_i*2^{k_i} i=lrai2ki
其中可以控制的就是那个k数组。
考虑贪心。
我们发现,对于一个正数,当然是希望它的k值越大。
对于一个负数,当然希望它的k值越小。
那么对于每个负数,它的k值最小为1(第一个位置为0)
然后一段正数区间则是递增的。

当然,这样是错误滴。为什么呢?我们有可能把一段正数区间乘2然后合并到前面的负数,这样答案会变大。
这样一直合并合并之后,可能会出现的情况是:k数组会分成一个一个块,每块开头是1(第一块开头为0)然后向后面递增。

所以我们从左到由依次加入数字,然后把当前的数字往前面合并,如果合并到最后当前块为负数就不合并了。
但是数值要去模啊?怎么办?我们发现,负数不会特别小,最少是-2e9,那么只要正数大于4e9的时候就可以往前面疯狂合并即可。
于是我们多记录一个数值表示当前块的数值堆4e9取min即可。

拿到70分的好成绩。

对于100分呢?
我们离线询问,把询问按照有端点排序,然后依次加入数字,做与上面相同的东东。
然后由于询问的左端点不是1,那么就不能直接统计了(废话)
那么这个左端点必定把某一块分割成两个部分。
求出这一块右边部分即可。

那么问题来了:分割后两个部分右边的部分不会被合并吗?
答案是:不会。
因为我们发现,左端点所在的块右边的块的数值是小于0的(否则在做的过程中就已经合并了),我们知道,数值小于0是不能合并的,因此不会合并。

那么利用并查集维护块或是线段树即可。

标程
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <cctype>
using namespace std;
const int maxn=500010;
const long long mo=1000000007;

struct node{
	long long le,ri,kk,gg,lazy;
};

int n,m,l[maxn],r[maxn],gs,now;
long long a[maxn],b[maxn],c[maxn],mi[maxn],sum[maxn],jl,jr,jk,jg;
long long le[maxn],ri[maxn],k[maxn],zs[maxn],aa[maxn],qsum[maxn],col[maxn],id[maxn],wz[maxn],jll[maxn],qh[maxn],zd[maxn];
node tree[4*maxn];

void down_lazy(int x)
{
	if (tree[x].lazy==1)
	{
		tree[x*2].le=tree[x].le;
		tree[x*2+1].le=tree[x].le;
		tree[x*2].ri=tree[x].ri;
		tree[x*2+1].ri=tree[x].ri;
		tree[x*2].kk=tree[x].kk;
		tree[x*2+1].kk=tree[x].kk;
		tree[x*2].lazy=tree[x].lazy;
		tree[x*2+1].lazy=tree[x].lazy;
		tree[x*2].gg=tree[x].gg;
		tree[x*2+1].gg=tree[x].gg;
		tree[x].lazy=0;
	}
}

void change(int x,int l,int r,int st,int en,int le,int ri,int kk,int g)
{
	if (l==st && r==en)
	{
		tree[x].le=le;
		tree[x].ri=ri;
		tree[x].kk=kk;
		tree[x].gg=g;
		tree[x].lazy=1;
	}
	else
	{
		down_lazy(x);
		int mid=(l+r)/2;
		if (mid>=en) change(x*2,l,mid,st,en,le,ri,kk,g);
		else if (mid<st) change(x*2+1,mid+1,r,st,en,le,ri,kk,g);
		else
		{
			change(x*2,l,mid,st,mid,le,ri,kk,g);
			change(x*2+1,mid+1,r,mid+1,en,le,ri,kk,g);
		}
	}
}

void find(int x,int l,int r,int st)
{
	if (l==r)
	{
		jl=tree[x].le;
		jr=tree[x].ri;
		jk=tree[x].kk;
		jg=tree[x].gg;
	}
	else
	{
		down_lazy(x);
		int mid=(l+r)/2;
		if (mid>=st) find(x*2,l,mid,st);
		else find(x*2+1,mid+1,r,st);
	}
}

long long min(long long a,long long b)
{
	if (a<b) return a;return b;
}

void qsort(int ll,int rr)
{
	int i=ll;int j=rr;
	long long m=r[(i+j)/2];
	while (i<=j)
	{
		while (r[i]<m) i++;
		while (r[j]>m) j--;
		if (i<=j)
		{
			swap(r[i],r[j]);
			swap(l[i],l[j]);
			swap(wz[i],wz[j]);
			i++;j--;
		}
	}
	if (ll<j) qsort(ll,j);
	if (rr>i) qsort(i,rr); 
}

long long qsm(long long a,long long b)
{
	long long t=1;
	long long y=a;
	while (b>0)
	{
		if ((b&1)==1) t=t*y%mo;
		y=y*y%mo;
		b/=2;
	}
	return t;
}

int main()
{
//	freopen("data.in","r",stdin);
//	freopen("data.out","w",stdout);
	freopen("standard.in","r",stdin);
	freopen("standard.out","w",stdout);
	mi[0]=1;
	for (int i=1;i<=500000;i++)
	{
		mi[i]=mi[i-1]*2%mo;
	}
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		scanf("%l64d",&a[i]);
		zd[i]=(zd[i-1]+a[i]*mi[i]%mo)%mo;
	}
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d",&l[i],&r[i]);
	}
	for (int i=1;i<=m;i++)
	{
		wz[i]=i;
	}
	qsort(1,m);
	k[1]=0;le[1]=1;ri[1]=1;zs[1]=a[1];gs=1;aa[1]=a[1];qh[1]=(a[1]+mo)%mo;
	change(1,1,n,le[gs],ri[gs],le[gs],ri[gs],k[gs],gs);
	int op=1;
	while (r[op]==1)
	{
		jll[wz[op]]=(qh[1]+mo)%mo;
		op++;
	}
	for (int j=2;j<=n;j++)
	{
		long long zss=a[j]*2;long long aaa=a[j]*2%mo;
		int kk=1;
		if (a[j]>0)
		{
			while (gs>0 && zs[gs]+zss*mi[k[gs]]>0)
			{
				zss=min(5000000000,zs[gs]+zss*mi[k[gs]]);
				aaa=(aa[gs]+aaa*mi[k[gs]]%mo+mo)%mo;
				kk=kk+k[gs];
				gs--;
			}
			if (gs==0)
			{
				gs++;
			}
			else
			{
				zss=min(5000000000,zs[gs]+zss*mi[k[gs]]);
				aaa=(aa[gs]+aaa*mi[k[gs]]%mo+mo)%mo;
				kk=k[gs]+kk;
			}
			ri[gs]=j;
			k[gs]=kk;
			zs[gs]=zss;
			aa[gs]=aaa;
			qh[gs]=(qh[gs-1]+aaa+mo)%mo;
		}
		else
		{
			gs++;
			le[gs]=j;
			ri[gs]=j;
			zs[gs]=zss;
			k[gs]=kk;
			aa[gs]=aaa;
			qh[gs]=(qh[gs-1]+aaa+mo)%mo;
		}
		change(1,1,n,le[gs],ri[gs],le[gs],ri[gs],k[gs],gs);
		while (op<=m && j==r[op])
		{
			if (l[op]==r[op])
			{
				jll[wz[op]]=a[l[op]];
				op++;
			}
			else
			{
				jr=0;jl=0;jk=0;jg=0;
				if (l[op]>1)
				{
					find(1,1,n,l[op]-1);
					jll[wz[op]]=(qh[gs]-qh[jg]+mo)%mo;
					int opt=0;
					if (jk==jr-jl+1) opt=1;
					jll[wz[op]]=(jll[wz[op]]+(zd[jr]-zd[l[op]-1]+mo)%mo*qsm(mi[jr-(jk-(l[op]-jl))+opt],mo-2)%mo)%mo;
					
					int pd=jl;
					jr=0;jl=0;jk=0;jg=0;
					find(1,1,n,l[op]);
					if (jl!=pd)
					{
						jll[wz[op]]=(jll[wz[op]]-(zd[jr]-zd[jl-1]+mo)%mo*qsm(mi[jr-jk+1],mo-2)%mo)%mo;
					}
					op++;
				}
				else
				{
					jll[wz[op]]=(qh[gs]+mo)%mo;
					op++;
				}
			}
		}
	}
	for (int i=1;i<=m;i++)
	{
		printf("%l64d\n",(jll[i]+mo)%mo);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值