生成函數、組合數學

[ARC089D] ColoringBalls

咕着
咕着

天,感覺全是細節,事實上也如此:
借大佬的細節才過了此題

#include<bits/stdc++.h>
#define maxn 75
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 1000000007
#define vi vector<int>
#define pb push_back
using namespace std;

int n,K,fac[maxn << 2],invf[maxn << 2],inv[maxn << 2];
int C(int a,int b){ if(a<0 || b<0 || a-b<0) return 0;return fac[a] * 1ll * invf[b] % mod * invf[a-b]%mod; }
char s[maxn];
vi p;int ans;

bool check(){
	static bool vis[maxn];
	static int loc[maxn];
	memset(vis,0,sizeof vis);
	int j=1;
	for(int i=p.size()-1;i>=0 && p[i];i--){
		for(;j <= K && s[j] != 'r';j++);
		if(j > K) return 0;
		vis[j] = 1 , loc[i] = j , j++;
	}
	j=1;
	for(int i=p.size()-1;i>=0 && p[i];i--){
		j = max(j , loc[i]);
		for(;j <= K && s[j] != 'b';j++);
		if(j > K) return 0;
		vis[j] = 1 , loc[i] = j , j++;
	}
	j=1;
	for(int i=0;i<p.size() && p[i]==0;i++){
		for(;j <= K && (s[j] != 'r' || vis[j]);j++);
		if(j > K) return 0;
		vis[j] = 1 , j++;
	}
	j=1;
	for(int i=p.size()-1;i>=0 && p[i] > 1;i--){
		j = max(j , loc[i]);
		rep(k,2,p[i]){
			for(;j <= K && vis[j];j++);
			if(j > K) return 0;
			vis[j] = 1 , j++;
		}
	}
	return 1;
}

void dfs(int sz,int mx,int lim){
	if(sz > n) return;
	if(check()){
		int sm = fac[p.size()];
		for(int i=0,j;i<p.size();i=j){
			for(j=i;j<p.size() && p[j] == p[i];j++);
			sm = 1ll * sm * invf[j-i] % mod;
		}
		ans = (ans + 1ll * C(n-sz+mx-1,mx-1) * sm) % mod;
		//printf("%d %d %d %d\n",sm,ans,sz,mx);
	}
	else return;
	rep(i,lim,(n-sz+1)/2){
		p.pb(i);
		dfs(sz+max(2*i-1,1)+(sz!=0),mx+2*i+2,i);
		p.pop_back();
	}
}

int main(){//freopen("1.in","r",stdin);//freopen("2.out","w",stdout);
	scanf("%d%d",&n,&K);
	scanf("%s",s+1);
	fac[0] = inv[0] = inv[1] = fac[1] = invf[0] = invf[1] = 1;
	rep(i,2,(maxn << 2) - 1) fac[i] = 1ll * fac[i-1] * i % mod , inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod,
		invf[i] = 1ll * invf[i-1] * inv[i] % mod;
	dfs(0,1,0);
	printf("%d\n",(ans+mod)%mod);
}

[ARC096C] Everything on It

在這裏插入圖片描述
容斥,枚舉有kk種元素用了1\leq 1次,其中有jj種元素用了11次。
則答案爲:
k=0n(1)k(nk)22nkj=0k(kj)p=0j{jp}2(nk)p\sum_{k=0}^n(-1)^k\binom nk2^{2^{n-k}}\sum_{j=0}^k \binom kj\sum_{p=0}^j \begin{Bmatrix}j\\p\end{Bmatrix}2^{(n-k)p}
注意22nk2^{2^{n-k}}(22)nk(2^2)^{n-k}是完全不同的。
注意到有恆等式j=pk(kj){jp}={k+1p+1}\sum_{j=p}^k\binom kj \begin{Bmatrix} j\\p \end{Bmatrix}=\begin{Bmatrix} k+1\\p+1 \end{Bmatrix}
證明:考慮組合意義,相當於是拿kjk-j個數不選,剩下的jj個數分到pp組內,如果我們加一個組來裝着kjk-j個數,那麼這個組可能爲空,同時發現這個組和別的組不一樣,但是第二類斯特林數組之間是沒有標號區別的,所以我們就往空組加入一個00(強行規定00所在的組爲不選組),則非空和有區別這兩個限制都被滿足了,方案數就是{k+1p+1}\begin{Bmatrix} k+1\\p+1 \end{Bmatrix}
但是其實這樣做很傻,直接類似第二類斯特林數dpdp
gi,j=gi1,j1+(j+1)gi1,jg_{i,j} = g_{i-1,j-1} + (j+1)g_{i-1,j}
後面那個j+1j+1中的+1+1就代表了不選這一選項,直接dpdp也沒有什麼問題。

ans=k=0n(1)k(nk)22nkp=0k{k+1p+1}2(nk)pans = \sum_{k=0}^n(-1)^k\binom nk2^{2^{n-k}}\sum_{p=0}^k \begin{Bmatrix}k+1\\p+1\end{Bmatrix}2^{(n-k)p}

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 3005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,P,S[maxn][maxn],C[maxn][maxn],pw[maxn];
int upd(int x){ return x += x >> 31 & P; }
int main(){
	scanf("%d%d",&n,&P);
	rep(i,S[0][0]=C[0][0]=1,n) rep(j,C[i][0]=1,i) C[i][j] = upd(C[i-1][j-1] + C[i-1][j] - P) % P;
	rep(i,1,n) rep(j,S[i][0]=1,i) S[i][j] = (S[i-1][j] * (j+1ll) + S[i-1][j-1]) % P;
	int ans = 0 , ppw = 2;
	rep(i,0,n) pw[i] = 1;
	per(k,n,0){
		int sm = 0;
		rep(i,0,k) sm = (sm + 1ll * S[k][i] * pw[i]) % P;
		ans = (ans + (k&1?-1ll:1ll)*C[n][k]*ppw%P*sm)%P;
		int p2 = 1;
		rep(i,0,n) pw[i] = 1ll * p2 * pw[i] % P , p2 = 2ll * p2 % P;
		ppw = ppw * 1ll * ppw % P;
	}
	printf("%d\n",(ans+P)%P);
}

CF1295F Good Contest

題意:aia_i爲在[Li,Ri][L_i,R_i]之間均勻隨機的離散變量,求aia_i不增的概率。

先離散化,將L,RL,R離散化後排序得到一個數組aa
fi,jf_{i,j}表示前ii個變量最後一個變量aj\geq a_j的概率。
那麼fi,j=fk,j+1×(aj+1aj+ik1ik)f_{i,j} = \sum f_{k,j+1} \times \binom {a_{j+1}-a_j+i-k-1}{i-k}
後面那個組合數就是在[aj,aj+1)[a_j,a_{j+1})中選擇jkj-k個可以相同的數但是需要無序的方案。(因爲最後分配給k+1...ik+1...i的變量是有序的。)
注意這個轉移必須要k+1...ik+1...i都包含[aj,aj+1)[a_j,a_{j+1})這個區間纔行。

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 105
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 998244353
using namespace std;

int n,l[maxn],r[maxn],sb[maxn<<1];
int C[maxn][maxn],inv[maxn],f[maxn][maxn],in[maxn][maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod; return r; }

int main(){
	inv[0] = inv[1] = 1;
	int sm = 1;
	for(int i=2;i<maxn;i++) inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&l[i],&r[i]),sb[++sb[0]]=l[i],sb[++sb[0]]=(++r[i]) , sm = 1ll * sm * Pow(r[i] - l[i] , mod-2) % mod;
	sort(sb+1,sb+1+sb[0]);
	sb[0] = unique(sb+1,sb+1+sb[0])-sb-1;
	for(int i=1;i<sb[0];i++){
		int L = sb[i+1] - sb[i] , f = 1;
		rep(j,1,n){
			f = 1ll * f * (L+j-1) % mod * inv[j] % mod;
			C[i][j] = f;
		} 
	}
	rep(i,1,sb[0]) f[0][i] = sm;
	for(int i=1;i<=n;i++){
		l[i] = lower_bound(sb+1,sb+1+sb[0],l[i])-sb , r[i] = lower_bound(sb+1,sb+1+sb[0],r[i])-sb;
		rep(j,l[i],r[i]-1){
			in[i][j] = 1;
			for(int k=i-1;in[k+1][j] && k>=0;k--)
				f[i][j] = (f[i][j] + 1ll * f[k][j+1] * C[j][i-k]) % mod;
		}
		per(j,sb[0]-1,1) f[i][j] = (f[i][j] + f[i][j+1]) % mod;
	}
	printf("%d\n",(f[n][1]+mod)%mod);
}

Valentines Day Contest 2020 C. Isolation

給出(x,y)(x,y),求走nn步(每步可以讓橫座標或縱座標±1\pm 1)的方案數使得途中不經過距離原點曼哈頓距離爲D4D \leq 4的點。

容斥,設fi,j,kf_{i,j,k}表示走了ii步之後到達了jj號禁止點(禁止點爲距離原點曼哈頓距離爲DD的點集),至少一共走過了kk個禁止點。

所以答案就是4n+(1)kfi,j,k4ni4^n + \sum (-1)^k f_{i,j,k}4^{n-i}
注意到我們直接計算容斥係數可以不需要kk這一維。
所以fi,jf_{i,j}在我們可以O(1)O(1)直接計算兩個點之間走pp步到達的方案數後,
複雜度就是O(n2D2)O(n^2D^2)的。
設一個點在(a,b)(a,b)另一個點在(c,d)(c,d),一共走pp步要到達。
發現因爲有相反的方向(減少的方向)所以計數很困難,考慮如何用上一共走pp步的條件。

如果我們把減少橫座標看做增加縱座標,減少縱座標看做增加橫座標,
那麼橫座標的增加量比縱座標的增加量大ca+bdc-a+b-d,總步數又是pp,所以我們可以得出增加縱座標的次數是p(ca+bd)2\frac {p - (c-a+b-d)}2,方案數爲(pp(ca+bd)2)\binom {p}{\frac {p - (c-a+b-d)}2}
但是這樣無法區分增加縱座標和減少橫座標這兩種操作(等),
於是我們再反着定義:
把減少橫座標看做增加橫座標,減少縱座標看做增加橫座標,增加橫座標看做增加縱座標,增加縱座標不變。
那麼縱座標的增加量比橫座標的增加量大ca+dbc-a+d-b
方案數爲(pp(ca+db)2)\binom {p}{\frac {p - (c-a+d-b)}2}
然後發現這樣定義,兩個組合數的乘積就是我們需要的答案。
如果我們記橫座標之差爲xx,縱座標之差爲yy的話答案就是:
(ppxy2)(ppx+y2)\binom{p}{\frac {p-x-y}2} \binom{p}{\frac {p-x+y}2}
這個方法感覺有點奇怪,特別是拓展到三維的時候他是四個組合數乘起來。
但是他可以O(1)O(1)

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 3005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 1000000007
using namespace std;

int X,Y,n,D,c[maxn][maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1)r=1ll*r*b%mod;return r; }
int C(int n,int m){ if(m < 0 || n < 0 || n - m < 0) return 0; return c[n][m]; }
int B(int a,int b,int p){
	if(p-a-b&1) return 0;
	return C(p,(p-a-b)/2) * 1ll * C(p,(p-a+b)/2) % mod;
}
int x[maxn],y[maxn],cnt;
int f[maxn][maxn];

int main(){
	scanf("%d%d%d%d",&X,&Y,&n,&D);
	rep(i,c[0][0]=1,maxn-1) rep(j,c[i][0]=1,i) c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod;	
	rep(i,-D,D) rep(j,-D,D) if(abs(i) + abs(j) == D)
		x[++cnt] = i , y[cnt] = j;
	rep(i,1,n) rep(j,1,cnt){
		f[i][j] = -B(abs(x[j]-X),abs(y[j]-Y),i);
		rep(k,1,i-1) rep(p,1,cnt)
			f[i][j] = (f[i][j] - 1ll * f[k][p] * B(x[j]-x[p],y[j]-y[p],i-k)) % mod;
	}
	int ans = Pow(4 , n);
	rep(i,1,n) rep(j,1,cnt) ans = (ans + f[i][j] * 1ll * Pow(4,n-i)) % mod;
	printf("%d\n",(ans+mod)%mod);
}

AGC034F RNG and XOR

AiA_i概率選ii來異或你手上的數,問第一次得到1....2n11....2^n-1的期望時間。

怎麼感覺ZJOI2019開關就是把這道題的FWT拆了拆式子快速實現。
發現多次到達同一個數很難解決,但是發現如果倒過來我們求從1...2n11...2^n-1第一次到00的時間並且讓00不能轉移出來就可以解決多次到達同一個數的問題,因爲這樣同一個數都變成了00,便於統一處理。
所以設fif_i表示從ii第一次到00的時間。
則有fi=jfj×Aji+1f_i = \sum_{j} f_j \times A_{j\wedge i} + 1,注意這個式子是對ii進行轉移,所以i>0i \gt 0
寫成生成函數就是(f0,f1...f2n1)(A0,A1...A2n1)=(f0+2n1,f11,f21....f2n11)(f_0,f_1...f_{2^n-1}) \oplus(A_0,A_1...A_{2^n-1}) = (f_0+2^n-1,f_1-1,f_2-1....f_{2^n-1}-1)
注意f0+2n1f_0+2^n-1不是由上面的方程得到的,而是因爲AA的和爲11,所以卷積之後所有項的和不變,所以解出來的。
那麼我們將A0A_0變成A01A_0-1則可以發現。(f0,f1...f2n1)(A01,A1...A2n1)=(2n1,1,1....1)(f_0,f_1...f_{2^n-1}) \oplus(A_0-1,A_1...A_{2^n-1}) = (2^n-1,-1,-1....-1)
直接寫FWTFWT除法即可,注意到Ai1=0\sum A_i - 1 = 0也就是說我們FWTFWT後被除的式子中2n12^n-1這項爲00(因爲Ai1A_i \geq 1別的都不爲00),注意到這東西表達的方程是f0+f1+...f2n1×0=0f_0+f_1+...f_{2^n-1} \times 0 = 0,我們考慮就這樣讓f0+f1+...f2n1=0f_0+f_1+...f_{2^n-1}=0,發現這個方程可能會無解,因爲我們加多了條件,但是沒關係,我們解方程是用IFWTIFWT解,不會考慮無解。
IFWTIFWT之後如果得到了f0,f1,f2...f2n1f_0',f_1',f_2'...f_{2^n-1}',考慮怎麼得到f0,f1...f2n1f_0,f_1...f_{2^n-1},假設真實的f0+f1+...f2n1=Xf_0+f_1+...f_{2^n-1} = X
那麼在FWTFWT之後fi=fiX2nf_i' = f_i - \frac {X}{2^n},因爲f0=f0X2n=X2nf_0' = f_0 - \frac X{2^n} = -\frac X{2^n}
所以每個位置都減去f0f_0'即可。

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 1<<18|5
#define mod 998244353
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,N;
int a[maxn],b[maxn];

int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod; return r; }
void FWT(int *a){
	rep(i,0,n-1) rep(j,0,N-1) if(!(j>>i&1)){
		int v = j ^ (1 << i) , x = (a[j] + a[v]) % mod , y = (a[j] - a[v]) % mod;
		a[j] = x , a[v] = y;
	}
}

int main(){
	scanf("%d",&n);N = 1 << n;int S=0;
	rep(i,0,N-1) scanf("%d",&a[i]),S+=a[i];
	S = Pow(S , mod-2);
	rep(i,0,N-1) a[i] = 1ll * a[i] * S % mod;
	a[0]-- , b[0] = 1 << n;
	rep(i,0,N-1) b[i]--;
	FWT(a),FWT(b);
	rep(i,0,N-1) a[i] = 1ll * Pow(a[i],mod-2) * b[i] % mod;
	FWT(a);int ivN = Pow(N , mod-2);
	rep(i,0,N-1) a[i] = 1ll * a[i] * ivN % mod;
	per(i,N-1,0) a[i] = (a[i] - a[0]) % mod;
	rep(i,0,N-1) printf("%d\n",(a[i]+mod)%mod);
}

2019-2020 XX Opencup GP of Tokyo E . Count Modulo 2

給出A1...AKA_1...A_K,求a1+a2...+an=Sa_1+a_2...+a_n = S的方案數mod2\bmod 2,其中aia_iA1...AkA_1...A_k中的一個,K200Ai1e5,n1e18,S1e18K\leq 200 , A_i \leq 1e5, n\leq 1e18 , S \leq 1e18

活生生猜出來的解法
因爲要mod2\bmod 2,所以假設說一個方案中A1...AkA_1...A_k各有b1..bkb_1..b_k個,那麼和他只是順序不同的方案數有n!bi!1\frac {n!}{\prod b_i!}-1種。
由庫默爾定理我們可以知道一個二進制位kk只能由一個bib_i所擁有,這樣就可以解決n1e18n \leq 1e18的問題,把nn的每個二進制位拿出來即可。(有個更牛逼的結論,在(mod2)\pmod 2意義下f(x2)f(x)2f(x^2) \equiv f(x)^2
但是S1e18S \leq 1e18
考慮Ai1e5A_i \leq 1e5,所以當我們在考慮第kk位的時候,剩餘的S>2k1e5S \gt2^k1e5後面都無法讓SS變爲00
所以我們從大到小枚舉kk,用bitsetbitset保存1e51e5位,然後轉移,kk--的時候需要枚舉每個bitsetbitset位來轉移,時間複雜度O(logn(Kv32+v))O(\log n(\frac {Kv}{32}+v))

AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
using namespace std;

LL n,S;
int a[maxn],K;
bitset<maxn>f[2],t;

int main(){	
	int T;
	for(scanf("%d",&T);T--;){
		scanf("%lld%lld%d",&n,&S,&K);
		rep(i,1,K) scanf("%d",&a[i]);
		int now = 1 , pre = 0;
		f[now].reset(),f[pre].reset();
		f[now][0] = 1;
		per(i,60,0){
			swap(now,pre);
			t.reset(),f[now].reset();
			if(n >> i & 1){
				rep(j,1,K) t ^= f[pre] >> a[j];
			}
			else t = f[pre];
			if(i){
				rep(j,0,(maxn-4)/2) if(t[j])
					f[now].flip(j << 1 | (S >> (i-1) & 1));
			}
			else 
				f[now] = t;
		}
		cout << f[now][0] << endl;
	}
}

2019-2020 XX Opencup GP of Tokyo I. Amidakuji

Klog2n+1K \leq \lceil \log_2n \rceil + 11...n1...n的排列的映射pi(x)p_i(x),對於所有x,yx,y都存在qK(qK1(...q1(x)))=yq_K(q_{K-1}(...q_1(x))) = y,其中qi(x)=pi(x)q_i(x) = p_i(x)pi(x)1p_i(x)^{-1}

P=log2nP = \lfloor \log_2n \rfloor
則我們讓pi,j=j+2imodn,i[0,P]p_{i,j} = j + 2^i \bmod n,i \in [0,P]
這樣可以組合出來nn以內所有奇數,包括負的。
證明可以考慮歸納證明。
如果nn是奇數,那麼正負兩邊已經能讓我們到達所有位置。
如果nn是四的倍數,可以構造排列{2,3,0,1,6,7,4,5....n2,n1,n4,n3}\{2,3,0,1,6,7,4,5....n-2,n-1,n-4,n-3\}來讓我們可以使得一個位置移動奇數或偶數,注意這時這個排列是可以讓x,yx,y的奇偶性不同的,所以2P2^{P}這個排列就不需要了(剛好卡進),我們只需要找距離最近也就是<2P\lt 2^P的那邊走。
如果nnmod4=2\bmod 4 = 2,可以構造排列 {2,3,0,1,6,7,4,5....n4,n3,n6,n5,n2,n1}\{2,3,0,1,6,7,4,5....n-4,n-3,n-6,n-5,n-2,n-1\}{0,1,2,3,....n6,n5,n2,n1,n3,n4}\{0,1,2,3,....n-6,n-5,n-2,n-1,n-3,n-4\}即可。
AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 1005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
#define pb push_back
using namespace std;

int n;
vector<vector<int> >p;

int main(){
	scanf("%d",&n);
	if(n == 2){
		puts("-1");
		return 0;
	}
	int L = 1 , l = 0;
	for(;L <= n; L<<=1,l++);
	l--;
	rep(i,0,l-(n % 2 == 0)){
		vector<int>r;
		rep(j,0,n-1)  r.pb((j+(1<<i)) % n);
		p.pb(r);
	}
	if(n % 4 == 0){
		vector<int>r;
		rep(j,0,n/4-1)
			r.pb(4*j+2),r.pb(4*j+3),r.pb(4*j+1),r.pb(4*j);
		p.pb(r);
	}
	else if(n % 2 == 0){
		vector<int>r;
		rep(j,0,n/4-1)
			r.pb(4*j+2),r.pb(4*j+3),r.pb(4*j+1),r.pb(4*j);
		r.pb(n-2),r.pb(n-1);
		p.pb(r);
		r.clear();
		rep(j,0,n-5) r.pb(j);
		r.pb(n-4+2),r.pb(n-4+3),r.pb(n-4+1),r.pb(n-4);
		p.pb(r);
	}
	printf("%d\n",p.size());
	rep(i,0,p.size()-1)
		rep(j,0,n-1)
			printf("%d%c",p[i][j]+1," \n"[j==n-1]);
}

CodeForces 1292F Nora’s Toy Boxes

題意:給出n60n\leq 60個不同的60\leq 60的數,當i,j,ki,j,k滿足ai,aj,aka_i,a_j,a_k都未被刪去,aiaja_i | a_j並且aiaka_i | a_k時可以將aka_k刪去,求能刪除最多數的刪除序列數。

aiaja_i | a_j視作iji \rightarrow j的連邊,則我們對於每個弱連通圖分別計算方案。
(注意下文的討論中圖是弱聯通的。)
顯然這個如果ij,jki\rightarrow j,j\rightarrow k有邊,則iki \rightarrow k有邊。
所以如果在某次刪除中需要找到一個ii,這個ii一定可以沒有入度。
我們把沒有入度的點集看做SS,其他點看做TT
則我們需要求最少刪到還有多少點,換個方向考慮,假如一開始我們讓一些點存在,然後讓這些i,j,ki,j,kai,aja_i,a_j存在,aka_k不存在的拓展出aka_k存在,如果能拓展出所有點,那麼這個拓展方案反過來就和合法的刪點方案一一對應。
首先SS中的點不可能被刪,所以SS中的點一開始都是存在的,刪最多的點意味着TT中一開始存在的點要儘量少,最少爲11,接下來我們給出構造的方案使得TT中一開始的點數爲11
對於TT中存在的點,找能夠到達他的所有的SS中的點xx,那麼xx能到達的點都會變爲存在,再重複這個過程即可,容易發現弱連通圖中的所有點都會變爲存在,也就是TT中任意一個點開始我們都可以讓所有點存在。
考慮如何根據這個過程構造出刪點方案,我們定義一種新的標記,這種標記只會打在SS的點中,對於TT中一開始的點,我們找能夠到達他的所有的SS中的點xx,給xx打上標記,之後我們找下一個被刪除的點yy,這個yy只需要保證能夠到達他的所有的SS中的點存在一個pppp是有標記的即可,接下來給能夠到達他的所有的SS中的點xx打上標記,如此重複即可得到一個刪點方案。
可以證明SS中的點數604\leq \frac {60}4,首先可以認爲SS中的點都應該30\leq 30,否則沒有出邊也沒有入邊不滿足強聯通,對於30\leq 30的所有點,SS中的點構成了一個反鏈,其大小1...30\leq 1...30的最小鏈覆蓋,我們可以給出一個鏈覆蓋爲{1,2,4,8....},{3,6,12}...\{1,2,4,8....\},\{3,6,12\}...等形如一個奇數a×2ka\times 2^k302\frac {30}2條鏈,所以SS中的點數15\leq 15
於是我們用一個狀壓dpdpfs,if_{s,i}表示目前集合ss的點打上了標記,已經刪去了ii個點,
注意到我們不應該把TT中的點是否被選過納入狀態,所以我們需要有兩種轉移。
一種是刪去能到達他的只有ss中的點,那麼第一維不變,第二維+1+1,需要預處理出能到達他的只有ss中的點數cc,通過cic-i統計出還沒被刪的點數來做決策。
第二種是刪去能使ss變大的點並且這個點能被ss到達,這個點顯然不會被刪過,所以我們可以有一個O(215n2)O(2^{15} n^2)dpdp
AC Code\mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 65
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
#define mod 1000000007
#define vi vector<int>
#define pb push_back
using namespace std;

int n,a[maxn];
int F[maxn],in[maxn],C[maxn][maxn],sta[1<<15],f[1<<15],g[1<<15][maxn];
int Find(int u){ return !F[u] ? u : F[u] = Find(F[u]); }
vi G[maxn];

int main(){
	scanf("%d",&n);
	rep(i,1,n) scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	rep(i,1,n) rep(j,i+1,n) if(a[j] % a[i] == 0){
		int x = Find(j) , y = Find(i);
		in[j]++;
		if(x ^ y) F[x] = y;
	}
	rep(i,C[0][0]=1,n) rep(j,C[i][0]=1,i) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
	rep(i,1,n) G[Find(i)].pb(i);
	int ans = 1 , hd = 0;
	rep(i,1,n) if(G[i].size() > 1){
		vi S;
		for(int v:G[i]) if(!in[v]) S.pb(v);
		int N = 1 << S.size();
		memset(f,0,sizeof f) , memset(g,0,sizeof g);
		int cnt = 0;
		for(int v:G[i]) if(in[v]){
			cnt ++;
			rep(j,0,S.size()-1)
				if(a[v] % a[S[j]] == 0)
					sta[v] |= 1 << j;
			f[sta[v]] ++;
		}
		rep(i,0,S.size()-1) rep(j,0,N-1) if(j >> i & 1)
			f[j] += f[j-(1<<i)];
		g[0][0] = 1;
		rep(j,0,N-1) rep(k,0,cnt) if(g[j][k]){
			if(k < f[j]) g[j][k+1] = (g[j][k+1] + 1ll * g[j][k] * (f[j] - k)) % mod;
			for(int p:G[i]) if(in[p] && ((sta[p] & j) != sta[p]) && ((sta[p] & j) || j == 0)) 
				g[j | sta[p]][k+1] = (g[j|sta[p]][k+1] + g[j][k]) % mod;
		}
		ans = 1ll * ans * g[N-1][cnt] % mod * C[hd + cnt - 1][cnt - 1] % mod; 
		hd += cnt - 1;
	}
	printf("%d\n",ans);
}

HDU 6691 Minimum Spanning Trees

我的某遠古題解

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