1. 程式人生 > 其它 >P7116-[NOIP2020]微信步數【數學】

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