509迷宮

~Cyan~發表於2024-09-07

想法還是太過於巧妙了。

首先有一個很簡單的容斥 \(n^2\) 做法。

然後我們能發現 \(mod\) 很小,注意:\(\forall_{1 \le i < mod}\) \(C_{mod}^{i} = 0\)

所以就有個天才的做法,將矩陣沿著對角線切開,類似這樣:

image

如果我們每隔 \(mod\) 進行一次切割,那麼我們就會發現如果把第 \(i\) 條對角線轉移到 第 \(i+1\) 條的話,就像這樣:

image

我們發現對於 \((x,y)\) 只需要轉移到 \((x+mod,y)\)\((x,y+mod)\) 就行了,因為其他點的組合數為 \(0\)

所以 \(dp_{x,i}\) 表示點 \((i,x-i)\) 的方案數,然後轉移就是:

  1. 對角線到對角線
  2. 對角線到障礙點
  3. 障礙點到障礙點
  4. 障礙點到對角線

分別做一下就行了。

點選檢視程式碼
#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
	int x=0,f=1; char c=getchar();
	while(!isdigit(c)){if(c=='-') f=-1; c=getchar();}
	while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
	return x*f;
}
const int N=3e5+5,mod=509,M=2e5+5;
int n,a[N];
int cc[mod+5][mod+5];
inline int C(int x,int y){return cc[x][y];}
inline void init(){
	cc[0][0]=1;
	for(int i=1;i<=mod;i++){
		cc[i][0]=1;
		for(int j=1;j<=mod;j++) cc[i][j]=(cc[i-1][j]+cc[i-1][j-1])%mod;
	}
}

int f[N],tmp[N],g[N];

vector<int> s[N*2];
signed main(){
    n=read();
	for(int i=1;i<=n;i++) a[i]=read(),s[i+a[i]].push_back(i);
	init(),f[0]=1;
	for(int i=1;i<=2*n/mod;i++){
		int l=(i-1)*mod,r=i*mod;
		int pl=max(0ll,l-n),pr=min(l,n);
		int ll=max(0ll,r-n),rr=min(r,n);
		//line to line
		for(int j=ll;j<=rr;j++){
			tmp[j]=f[j];
			if(j>=mod) (tmp[j]+=f[j-mod])%=mod; 
		}
		for(int j=l+1;j<=r;j++){
			for(auto x:s[j]){
				int L=l-a[x],R=x;
				//point to point
				for(int k=L;k<R;k++) if(l<k+a[k]&&k+a[k]<=r&&a[k]<=a[x]) (g[x]-=g[k]*C(x-k+a[x]-a[k],x-k))%=mod; //(k,a[k]) (x,a[x])

				//line to point
				for(int k=max(pl,L);k<=min(R,pr);k++) (g[x]+=f[k]*C(x+a[x]-l,x-k))%=mod; //(k,(i-1)*mod-k) (x,a[x])
				g[x]=(g[x]+mod)%mod;
				//point to line
				L=x,R=r-a[x];
				for(int k=max(L,ll);k<=min(R,rr);k++) (tmp[k]-=g[x]*C(r-a[x]-x,k-x))%=mod; //(x,a[x]) (k,i*mod-k)
			}
		}
		for(int i=0;i<=rr;i++) f[i]=(tmp[i]+mod)%mod,tmp[i]=0;
	}
	cout<<(f[n]+mod)%mod<<'\n';
}
/*
5
2 4 1 1 2 
*/

相關文章