【洛谷7116】[NOIP2020] 微信步数(自然数幂和)

点此看题面

  • 有一个\(k\)维场地,第\(i\)维范围为\([1,w_i]\)
  • \(n\)个操作,每个操作可以表示为将某一维的坐标\(±1\)
  • 你会先选定一个起点,然后不断重复依次执行这\(n\)个操作直至走出场地。
  • 场地中每个位置都会被作为一次起点,你想要知道从所有点出发执行操作次数的总和。
  • \(n\le5\times10^5,k\le10\)

真的想不到这次\(NOIP\)唯一一道做出来的题目居然是\(T4\)。。。

暴力思路

考虑对于每一个操作\(i\),我们统计以它为最后一个操作的总贡献。

首先一个最基本的结论,各维之间的位移是独立的,因此我们的对象就是这维最先越界的点

既然是暴力,那么我们就去枚举是在第\(x+1\)轮结束的,但发现第\(1\)轮的情况和之后的情况可能略有一些区别,需要单独讨论。

这里先设\(sum_t,smx_t,smn_t\)分别表示执行到第\(i\)个操作时第\(t\)维的位移、历史最大位置、历史最小位置。

然后类似地设\(tot_t,Mx_t,Mn_t\)分别表示执行完一轮操作之后第\(t\)维的位移、历史最大位移、历史最小位置。

方便起见,我们强制所有\(tot_t\ge0\)。这只要在\(tot_t<0\)时把所有第\(t\)维的操作都取反就可以了。

在第\(1\)轮结束

首先考虑它是否可能作为一个结束操作。

那么先要满足不存在某一维\(t\)使得\(smx_t-smn_t\ge w_t\),因为这就说明所有点都必然已经走出了。

否则,还需要满足当前的\(sum_{c_i}\)不在\([smn_{c_i},smx_{c_i}]\)范围内,因为在这范围内意味着这个位移之前已经达到过,就不可能在这一次操作越界。

如果这两个条件都满足了,就意味着这个操作可以作为结束操作,且因这个操作越界的点的第\(c_i\)维的坐标只有一种取值。

接着考虑其他维度,对于第\(t\)维,已经有\(smx_t-smn_t\)个点越界了,那么还有\(w_t-(smx_t-smn_t)\)种取值。

所以方案数就是:

\[\sum_{t\not=c_i}(w_t-(smx_t-smn_t)) \]

在第\(x+1\)轮结束

这里先讲一下第\(x+1\)轮时每一维位移实际上的历史最大值和历史最小值。

由于\(tot_t\)已经强制大于等于\(0\)了,那么历史最小值实际上就是\(Mn_t\)

而历史最大值既可能是上一轮的历史最大值,也可能是这一轮新的历史最大值,也就是\(\max\{tot_t\times(x-1)+Mx_t,tot_t\times x+smx_t\}\),也可以写作\(tot_t\times x+\max\{Mx_t-tot_t,smx_t\}\)

接着,我们依旧先考虑它是否可能作为一个结束操作。

第一个必要条件依旧是此时每一维依然都满足未全部越界,即每一维的历史最大值减历史最小值都不超过这一维的坐标范围。

第二个条件就是满足当前达到的位置之前从未到达过,需要满足\(tot_{c_i}\not=0,sum_{c_i}>smx_{c_i},Mx_t-tot_t<smx_{c_i}\),应该都是比较好理解的,这里就不多做解释了。

而此时的答案就是:

\[(nx+i)\sum_{t\not=c_i}(w_t-(tot_t\times x+\max\{Mx_t-tot_t,smx_t\}-Mn_t)) \]

暴拆多项式

首先在第\(1\)轮结束的贡献我们是可以直接做的,那么关键就是后面的部分了。

根据我们推出的答案式,发现其中很多项实际上都是常量,它其实就是\(k\)个关于\(x\)的二项式乘在了一起!

对于每一个操作我们直接暴力多项式乘法把这个多项式拆开,同时发现最多操作轮数\(p\)也是可以直接枚举统计的。

因此我们要做的就是求\(\sum_{x=1}^pf(x)\)

考虑把它按不同的幂次分别考虑,其实这就是\(k\)个自然数幂和!

那么直接拉格朗日插值与处理一下自然数幂和的系数就可以做了。

代码:\(O(nk^2)\)

//考场代码,可能有点丑陋
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 500000
#define K 10
#define X 1000000007
using namespace std;
const int f[11][15]=//打完表之后才想起拉格朗日插值的打表代码可以直接放代码里,但打都打完就算了
{
	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,500000004,500000004,0,0,0,0,0,0,0,0,0,0,0,0},
	{0,166666668,500000004,333333336,0,0,0,0,0,0,0,0,0,0,0},
	{0,0,250000002,500000004,250000002,0,0,0,0,0,0,0,0,0,0},
	{0,766666672,0,333333336,500000004,400000003,0,0,0,0,0,0,0,0,0},
	{0,0,916666673,0,416666670,500000004,166666668,0,0,0,0,0,0,0,0},
	{0,23809524,0,833333339,0,500000004,500000004,142857144,0,0,0,0,0,0,0},
	{0,0,83333334,0,708333338,0,583333338,500000004,125000001,0,0,0,0,0,0},
	{0,766666672,0,222222224,0,733333338,0,666666672,500000004,111111112,0,0,0,0,0},
	{0,0,450000003,0,500000004,0,100000000,0,750000006,500000004,700000005,0,0,0,0},
	{0,348484851,0,500000003,0,1,0,1000000006,0,833333340,500000004,818181824,0,0,0}
};
int n,k,c[N+5],d[N+5],w[K+5];
int tot[K+5],Mx[K+5],Mn[K+5],sum[K+5],smx[K+5],smn[K+5],g[K+5];
I void Solve()
{
	RI i,j,p;for(i=1;i<=n;++i) tot[c[i]]+=d[i],//模拟一轮操作统计tot,Mx,Mn
		Mx[c[i]]=max(Mx[c[i]],tot[c[i]]),Mn[c[i]]=min(Mn[c[i]],tot[c[i]]);
	for(i=1;i<=k;++i) if(tot[i]) break;if(i>k)
		for(i=1;i<=n;++i) if(Mx[i]-Mn[i]<w[i]) puts("-1"),exit(0);//判死循环
	for(i=1;i<=k;++i) if(tot[i]<0) for(tot[i]*=-1,//强制tot≥0
		Mx[i]*=-1,Mn[i]*=-1,swap(Mx[i],Mn[i]),j=1;j<=n;++j) if(c[j]==i) d[j]*=-1;
	RI s,t=0,ans=0,op,o,a,b;for(i=1;i<=n;++i)//枚举每个操作
	{
		if((sum[c[i]]+=d[i])>=smn[c[i]]&&sum[c[i]]<=smx[c[i]]) continue;//如果不可能产生贡献就跳过
		if(sum[c[i]]>smx[c[i]]) ++smx[c[i]],op=1;else --smn[c[i]],op=0;//更新历史最值,op记下是更新哪种最值,后面会用到
		if(smx[c[i]]-smn[c[i]]==w[c[i]])//如果这一个操作直接走完了
		{
			for(p=1,j=1;j<=k;++j) p=1LL*p*w[j]%X;ans=(1LL*(p-t+X)*i+ans)%X;goto Print;//用总点数减去已有点数,求出剩余总答案
		}
		for(s=j=1;j<=k;++j) if(j^c[i]) s=1LL*s*(w[j]-(smx[j]-smn[j]))%X;//第1轮结束
		t=(t+s)%X,ans=(1LL*i*s+ans)%X;//统计答案
		if(!tot[c[i]]||!op||Mx[c[i]]-tot[c[i]]>=smx[c[i]]) continue;//如果不可能在第x+1轮结束就跳过
		for(j=1;j<=k;++j) g[j]=0;g[0]=1;//清空多项式
		for(p=(w[c[i]]-(smx[c[i]]-Mn[c[i]]))/tot[c[i]],j=1;j<=k;++j) if(j^c[i])
		{
			a=tot[j],b=max(smx[j],Mx[j]-tot[j])-Mn[j];
			if(!a) {if(b>=w[j]) {p=0;break;}}else p=min(p,(w[j]-1-b)/a);//p统计最多进行轮数
			a=(X-a)%X,b=(w[j]-b+X)%X;
			for(s=k;~s;--s) g[s]=(1LL*b*g[s]+(s?1LL*a*g[s-1]:0))%X;//将这个二项式ax+b乘到总多项式上
		}
		if(p<=0) continue;//如果进行轮数小于等于0跳过
		for(s=1LL*g[0]*p%X,j=1;j<=10;s=(1LL*g[j]*a+s)%X,++j)
			for(b=1,a=o=0;o<=14;++o) a=(1LL*f[j][o]*b+a)%X,b=1LL*b*p%X;t=(t+s)%X;//统计点数
		for(s=k;~s;--s) g[s]=(1LL*i*g[s]+(s?1LL*n*g[s-1]:0))%X;//乘上二项式nx+i
		for(s=1LL*g[0]*p%X,j=1;j<=10;s=(1LL*g[j]*a+s)%X,++j)
			for(b=1,a=o=0;o<=14;++o) a=(1LL*f[j][o]*b+a)%X,b=1LL*b*p%X;ans=(ans+s)%X;//统计答案
	}
	Print:printf("%d\n",ans);
}
int main()
{
	freopen("walk.in","r",stdin),freopen("walk.out","w",stdout);
	RI i;for(scanf("%d%d",&n,&k),i=1;i<=k;++i) scanf("%d",w+i);
	for(i=1;i<=n;++i) scanf("%d%d",c+i,d+i);return Solve(),0;
}
posted @ 2020-12-09 20:51  TheLostWeak  阅读(91)  评论(0编辑  收藏