P7116-[NOIP2020]微信步数【数学】

正题

题目链接:https://www.luogu.com.cn/problem/P7116


题目大意

有一个\(k\)维空间,第\(i\)维长度为\(w_i\),有\(n\)步每一步都是让某个维的坐标\(+1/-1\),每次走完\(n\)步都会从\(1\)重新走一次,现在求从这个空间的每个点出发走多少步才能走出这个空间的步数的和。

\(1\leq n\leq 5\times 10^5\)

\(1\leq k\leq 10,1\leq w_i\leq 10^6\) 或者 \(1\leq k\leq 3,1\leq w_i\leq 10^9\)


解题思路

求在这一步出去的方案显然很麻烦,所以我们可以考虑对于每步之后求还没出去的起点数然后求个和就一样了。并且每个维度可以在一定程度上分开考虑。

第一轮显然需要特别处理,设\(l_{i,j},r_{i,j}\)表示第\(i\)步之后第\(j\)维的最小位移(非正数)和最大位移,那么这一步不会出去的方案就是\(\prod_{j=1}^k (w_j-r_{i,j}+l_{i,j})\),也就是在\([1-l,w-r]\)这个范围可以存活,这个可以暴力处理。

之后考虑每轮的第\(i\)步的最小/最大位移距离依旧记作\(l_{i,j},r_{i,j}\),之后考虑如何计算每一步多少轮之后会死亡,记作\(t\)

首先设\(a_i\)表示第一轮维度\(i\)缩小的范围,然后\(b_i\)表示之后每一轮这个维度缩小的范围,那么对于第\(i\)步来说,第\(j\)个维度的最久存活轮数就是\(\lfloor\frac{a_j-r_{i,j}+l_{i,j}}{b_j}\rfloor\),算出\(t\)之后假设如果我们能够枚举轮数的话那么答案应该就是

\[\sum_{x=1}^t\prod_{j=1}^ka_j-r_{i,j}+l_{i,j}-b_jx \]

显然可以\(O(k^2)\)暴力乘算得到一个和\(x\)有关的多项式然后求和。

至于多项式求和我们可以通过\(\sum_{x=0}^t x^j\)带入多项式暴算,可以直接拉插得到,当然这题可以对于\(k\leq 3\)的情况我们自己手动插多项式算,然后\(k>3\)的就直接预处理就好了。

时间复杂度:\(O(nk^2)\) 或者 \(O(nk^2+k\times max\{w_i\})\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=5e5+10,M=11,P=1e9+7;
ll n,m,ans,pw[M][N*2],l[N][M],r[N][M],w[M],a[M],b[M],e[M],f[M][M];
ll power(ll x,ll b){
	ll ans=1;
	while(b){
		if(b&1)ans=ans*x%P;
		x=x*x%P;b>>=1;
	}
	return ans;
}
ll calc(ll n,ll k){
	if(k>3)return pw[k][n];
	if(k==3)return (n*(n+1)/2%P)*((n*(n+1)/2)%P)%P;
	if(k==2)return n*(n+1)%P*(2*n+1)%P*((P+1)/6)%P;
	if(k==1)return n*(n+1)/2%P; 
	return n+1;
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(ll k=4;k<=m;k++)
		for(ll i=1;i<=1e6;i++)
			pw[k][i]=(pw[k][i-1]+power(i,k))%P;
	ans=1;
	for(ll i=1;i<=m;i++)
		scanf("%lld",&w[i]),ans=ans*w[i]%P;
	for(ll i=1;i<=n;i++){
		ll c,d;
		scanf("%lld%lld",&c,&d);e[c]+=d;
		for(ll j=1;j<=m;j++)
			l[i][j]=l[i-1][j],r[i][j]=r[i-1][j];
		l[i][c]=min(l[i][c],e[c]);
		r[i][c]=max(r[i][c],e[c]);
	}
	bool flag=1;
	for(ll i=1;i<=m;i++)
		if(e[i]!=0||w[i]-r[n][i]+l[n][i]<=0)
			{flag=0;break;}
	if(flag)return puts("-1")&0;
	for(ll i=1;i<=n;i++){
		ll sum=1;
		for(ll j=1;j<=m;j++)
			sum=sum*max(0ll,w[j]-r[i][j]+l[i][j])%P;
		ans=(ans+sum)%P;
	}
	for(ll i=1;i<=m;i++)a[i]=w[i]-r[n][i]+l[n][i];
	for(ll i=1;i<=n;i++)
		for(ll j=1;j<=m;j++){
			l[i][j]=min(0ll,l[i][j]+e[j]-l[n][j]);
			r[i][j]=max(0ll,r[i][j]+e[j]-r[n][j]);
		}
	for(ll i=1;i<=m;i++)b[i]=r[n][i]-l[n][i];
	flag=0;
	for(ll i=1;i<=n;i++){
		for(ll j=1;j<=m;j++)f[0][j]=0;
		f[0][0]=1;ll t=1e9+7;
		for(ll j=1;j<=m;j++){
			ll x=a[j]-r[i][j]+l[i][j];
			if(x<=0){flag=1;break;}
			if(b[j]>0)t=min(t,x/b[j]);
			for(ll k=0;k<=m;k++){
				(f[j][k]=f[j-1][k]*x%P)%=P;
				if(k)(f[j][k]+=f[j-1][k-1]*(P-b[j])%P)%=P;
			}
		}
		if(flag)break;
		for(ll j=0;j<=m;j++)
			(ans=ans+f[m][j]*calc(t,j)%P)%=P;
	}
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-10-07 16:37  QuantAsk  阅读(99)  评论(0编辑  收藏  举报