6464. 【GDOI2020模擬02.07】矩陣

題目

有個nmn*m的黑白的方格,根據這個矩陣求得ABCA、B、C三個數組
AiA_i表示第ii行的第一個黑格的位置(如果沒有就m+1m+1)。
BiB_i表示第ii列的第一個黑格的位置(如果沒有就n+1n+1)。
CiC_i表示第jj列的最後一個黑格的位置(如果沒有就00)。
求對於所有的塗色方案,不同的三元組(A,B,C)(A,B,C)的數量。
n8000n\leq 8000
m200m\leq 200


思考歷程

看到這題後沒有什麼特別的思路。
分析了一些粗淺的性質,對正解(甚至拿部分分)沒有什麼幫助。
最終就是打了個暴力。


正解

fi,jf_{i,j}表示前jj列中,強制ii行有黑格,所得到的方案數。
考慮從fi,jf_{i,j}轉移到fi+k,j+1f_{i+k,j+1}
k=0k=0
由於這ii列都有黑格,所以在新增一列時,無論第j+1j+1列怎麼塗,AA都不會再被更新。
所以只需要考慮BBCC的影響。
很顯然影響是1+i+Ci21+i+C_{i}^{2}
k>0k>0
爲了避免重複,新增的kk行填的黑格子都在j+1j+1列上。
原來的行的AiA_i不變,新增的行的Ai=j+1A_i=j+1,所以對於AA來說,方案數的不同在於新增kk列的位置。將kk列插入ii列中,得出的不同的AA乘上Ci+kkC_{i+k}^k種方案。
但是BBCC就可能要難搞一些,因爲原來的行在j+1j+1列上有可能塗黑。
換個角度想一想,把AABBCC綜合起來考慮。
分類討論一下:
一、假設Bj+1B_{j+1}Cj+1C_{j+1}都是新加的kk行之一。
每一種新增kk行插入原來的ii行的方案,都會對應着有且僅有一個Bj+1B_{j+1}Cj+1C_{j+1}的取值。
考慮上AA的影響,每個方案的AA都是不一樣的,所以在這個情況下,每種插入的方案都對應着唯一的(A,B,C)(A,B,C)
方案數爲Ci+kkC_{i+k}^k
二(三)、假設Bj+1B_{j+1}是原來的ii行之一,Cj+1C_{j+1}是新加的kk行之一(反之同理):
可以這麼理解:先把新增的kk行插入原來的ii行中,這時候對應着唯一的AA,所以不用擔心後面可能出現重複的情況。
插的第一個位置前面的行中任選一個作爲Bj+1B_{j+1}
硬算的話,就先枚舉第一個插入後的位置x+1x+1(表示這個位置前面有xx個原來的行),乘上xx的貢獻,然後將k1k-1行插入後面的i+kx1i+k-x-1行中的貢獻乘進去。列出個醜陋的式子,就是它的方案數。
通過組合數的定義來分析,這無非就是:在i+ki+k個東西中,在位置x+1x+1處選一個,然後它左邊選11個,右邊選k1k-1個。
這個東西不就是i+ki+k箇中選k+1k+1個嘛!
方案數出來了:Ci+kk+1C_{i+k}^{k+1}
四、假設Bj+1B_{j+1}Cj+1C_{j+1}都是原來的ii行之一
類似二的分析,得出方案數爲Ci+kk+2C_{i+k}^{k+2}
綜合起來,貢獻總共就是Ci+k+2k+2C_{i+k+2}^{k+2}
把式子列出來,顯然可以卷積。
NTT就完了。


代碼

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define L 16384
#define N 8010
#define M 210 
#define ll long long
#define mo 998244353
ll qpow(ll x,int y=mo-2){
	ll res=1;
	for (;y;y>>=1,x=x*x%mo)
		if (y&1)
			res=res*x%mo;
	return res;
}
int n,m;
ll fac[N],ifac[N];
ll f[N];
ll a[L],b[L],c[L];
int nN,lgn,re[L];
ll A[L],B[L],C[L];
inline void dft(ll A[],int flag){
	for (int i=0;i<nN;++i)
		if (i<re[i])
			swap(A[i],A[re[i]]);
	for (int i=1;i<nN;i<<=1){
		ll wn=qpow(3,(mo-1)/(2*i));
		if (flag==-1)
			wn=qpow(wn);
		for (int j=0;j<nN;j+=i<<1){
			ll wnk=1;
			for (int k=j;k<j+i;++k,wnk=wnk*wn%mo){
				ll x=A[k],y=wnk*A[k+i]%mo;
				A[k]=(x+y)%mo;
				A[k+i]=(x-y+mo)%mo;
			}
		}
	}
	if (flag==-1){
		ll inv=qpow(nN);
		for (int i=0;i<nN;++i)
			A[i]=A[i]*inv%mo;
	}
}
inline void multi(ll c[],ll a[],ll b[]){
	for (lgn=0,nN=1;nN<=2*n;nN<<=1,lgn++);
	re[0]=0;
	for (int i=1;i<nN;++i)
		re[i]=re[i>>1]>>1|(i&1)<<lgn-1;
	for (int i=0;i<nN;++i)
		A[i]=a[i],B[i]=b[i];
	dft(A,1),dft(B,1);
	for (int i=0;i<nN;++i)
		C[i]=A[i]*B[i]%mo;
	dft(C,-1);
	for (int i=0;i<nN;++i)
		c[i]=C[i];
}
int main(){
	freopen("matrix.in","r",stdin);
	freopen("matrix.out","w",stdout);
	scanf("%d%d",&n,&m);
	int most=max(n,m)+2;
	fac[0]=1;
	for (int i=1;i<=most;++i)
		fac[i]=fac[i-1]*i%mo; 
	ifac[most]=qpow(fac[most]);
	for (int i=most-1;i>=0;--i)
		ifac[i]=ifac[i+1]*(i+1)%mo;
	f[0]=1;
	for (int j=1;j<=m;++j){
		for (int i=0;i<=n;++i)
			a[i]=f[i]*ifac[i]%mo;
		b[0]=0;
		for (int k=1;k<=n;++k)
			b[k]=ifac[k+2];
		multi(c,a,b);
		for (ll i=0;i<=n;++i)
			f[i]=(c[i]*fac[i+2]+f[i]*(1+i+(i*(i-1)>>1)%mo))%mo;
	}
	ll ans=0;
	for (int i=0;i<=n;++i)
		ans+=f[i]*fac[n]%mo*ifac[i]%mo*ifac[n-i]%mo;
	printf("%lld\n",ans%mo);
	return 0;
}

總結

計數類的問題中,如果式子太複雜,就用定義的角度去分析它吧……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章