0601題目 (矩陣樹定理、2-sat、prufer序列)

A. Stranger Trees

題面

給定一棵有nn個節點的無根樹

求對於這nn個點,以及每個k=0,1,2...n1k=0,1,2...n-1,有多少棵由這n個點構成的帶標號無根樹,與給定的樹恰好有kk條邊重複。

答案模109+710^9+7

n100n\le100

題解

恰好kk條邊重複的方案爲g(k)g(k)至少kk條邊的方案爲f(k)f(k)

答案要求g(k)g(k),而f(k)=ki(ik)g(i)f(k)=\sum_{k\le i}\binom{i}{k}g(i)

二項式反演一下就是g(k)=ki(1)ik(ik)f(i)g(k)=\sum_{k\le i}(-1)^{i-k}\binom ikf(i)

所以答案轉化爲求f(k)f(k),可以通過樹形DP求出。

考慮已經確定了kk條邊。那麼nn個點被分成了nkn-k個連通塊。

有一個結論,就是共有nn個點,mm個聯通塊,每個連通塊裏點數爲aia_i的生成樹方案爲nm2i=1main^{m-2}\prod_{i=1}^ma_i那麼求這nkn-k個連通塊的生成樹方案就是nnk2ain^{n-k-2}\prod a_i

後面的乘積就是每個聯通塊選一個點的方案。

咋那麼就可以直接樹形DP,dp(i,j,0/1)dp(i,j,0/1)表示ii的子樹中,確定了jj條邊,ii所在連通塊是否已經選了點的方案。轉移就是樹形DP常見的轉移套路,時間複雜度O(n2)O(n^2)

如果11爲根DP,f(k)f(k)就等於dp(1,k,1)dp(1,k,1)

然後就做完了。時間複雜度O(n2)O(n^2)

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 105;
const int mod = 1e9 + 7;
vector<int>e[MAXN];
int n, sz[MAXN], f[MAXN][MAXN][2], tmp[MAXN][2], c[MAXN][MAXN];

inline int add(int x, int y) { return x+y>=mod ? x+y-mod : x+y; }
inline void inc(int &x, int y) { x = x+y>=mod ? x+y-mod : x+y; }
void dfs(int u, int ff) {
	sz[u] = 1;
	f[u][0][1] = f[u][0][0] = 1;
	for(int v, i = (int)e[u].size()-1; ~i; --i)
		if((v=e[u][i]) != ff) {
			dfs(v, u);
			for(int j = 0; j < sz[u]+sz[v]; ++j) tmp[j][0] = tmp[j][1] = 0;
			for(int j = 0; j < sz[u]; ++j)
				for(int k = 0; k < sz[v]; ++k) {
					inc(tmp[j+k+1][0], 1ll * f[u][j][0] * f[v][k][0] % mod);
					inc(tmp[j+k+1][1], (1ll * f[u][j][0] * f[v][k][1] + 1ll * f[u][j][1] * f[v][k][0]) % mod);
					inc(tmp[j+k][0], 1ll * f[u][j][0] * f[v][k][1] % mod);
					inc(tmp[j+k][1], 1ll * f[u][j][1] * f[v][k][1] % mod);
				}
			sz[u] += sz[v];
			for(int j = 0; j < sz[u]; ++j) f[u][j][0] = tmp[j][0], f[u][j][1] = tmp[j][1];
		}
}
inline int qpow(int a, int b) {
	int re = 1; b < 0 ? b += mod-1 : 0;
	for(; b; b>>=1, a=1ll*a*a%mod)
		if(b&1) re=1ll*re*a%mod;
	return re;
}
int main () {
	scanf("%d", &n);
	for(int i = 1, u, v; i < n; ++i)
		scanf("%d%d", &u, &v), e[u].push_back(v), e[v].push_back(u);
	dfs(1, 0);
	c[0][0] = 1;
	for(int i = 1; i < n; ++i) {
		c[i][0] = 1;
		for(int j = 1; j <= i; ++j)
			c[i][j] = add(c[i-1][j-1], c[i-1][j]);
	}
	for(int i = 0; i < n; ++i) f[1][i][1] = 1ll * f[1][i][1] * qpow(n, n-i-2) % mod;
	for(int i = 0; i < n; ++i) {
		int ans = 0, cf = 1;
		for(int j = i; j < n; ++j)
			inc(ans, 1ll * f[1][j][1] * cf % mod * c[j][i] % mod), cf = mod-cf;
		printf("%d ", ans);
	}
}

結論證明

考慮證明上面那個結論。

有兩種證法。一種是矩陣樹定理,由於比較麻煩且公式不好寫(太菜了)跳過。

pruferprufer序列來證明:

假設有nn個點,mm個連通塊。每個連通塊大小爲aia_i

把一個連通塊縮成一個點,那麼這一棵生成樹的pruferprufer序列長度爲m2m-2。如果連通塊ii的出現次數爲jj,那麼貢獻是aij+1a_i^{j+1}。那麼我們構造每個連通塊的EGFEGFF(ai)F(a_i)

Ans=(ai)((m2)![xm2]F(ai))=(ai)((m2)![xm2]eaix)=(ai)(m2)!(ai)m2(m2)!=ainm2\begin{aligned}Ans&=(\prod a_i)\left((m-2)![x^{m-2}]\prod F(a_i)\right)\\ &=(\prod a_i)\left((m-2)![x^{m-2}]\prod e^{a_ix}\right)\\ &=(\prod a_i)\frac{(m-2)!(\sum a_i)^{m-2}}{(m-2)!}\\ &=\prod a_i\cdot n^{m-2}\\ \end{aligned}

這道題就結束了。


B 白金元首與獨舞

在這裏插入圖片描述在這裏插入圖片描述
把每個格子向他指向的格子連邊,將外界看作一個點,那麼合法方案是一個有根的內向樹的形態。那麼就是有向生成樹計數,但是無法直接建圖,因爲點數是nmnm的。發現k300k\le 300,那麼就可以對於一個不確定的位置朝一個方向按照箭頭走,走到第一個不確定位置或者邊界再連邊,這樣點數是O(k)O(k)的,邊數也是O(k)O(k),就能夠過掉此題。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 205, MAXK = 305;
const int mod = 1e9 + 7;
const int dx[4] = { 1, -1, 0, 0 };
const int dy[4] = { 0, 0, 1, -1 };
int n, m, K, tar[MAXN][MAXN], num[MAXN][MAXN];

int a[MAXK][MAXK];
void link(int u, int v) { ++a[v][v], --a[u][v]; }
int dfs(int u, int v) {
	if(u < 1 || v < 1 || u > n || v > m) return K;
	if(tar[u][v]) return tar[u][v];
	return tar[u][v] = dfs(u+dx[num[u][v]], v+dy[num[u][v]]);
}
inline int qpow(int a, int b) {
	int re = 1;
	for(; b; b>>=1, a=1ll*a*a%mod) if(b&1) re=1ll*re*a%mod;
	return re;
}
int det(int N) {
	int re = 1;
	for(int j = 1; j <= N; ++j) {
		for(int i = j; i <= N; ++i)
			if(a[i][j]) {
				if(i^j) swap(a[i], a[j]), re = (mod - re) % mod;
				break;
			}
		for(int i = j+1; i <= N; ++i) {
			int t = 1ll * a[i][j] * qpow(a[j][j], mod-2) % mod;
			for(int k = j; k <= N; ++k)
				a[i][k] = (a[i][k] - 1ll * t * a[j][k]) % mod;
		}
		re = 1ll * re * a[j][j] % mod;
	}
	return re;
}
int fa[MAXN*MAXN];
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int main () {
	char s[MAXN];
	int T; scanf("%d", &T); while(T--) {
		scanf("%d%d", &n, &m); K = 0;
		for(int i = 1; i <= n; ++i) {
			scanf("%s", s+1);
			for(int j = 1; j <= m; ++j) { fa[(i-1)*m+j] = (i-1)*m+j;
				num[i][j] = (s[j] == 'L' ? 3 : s[j] == 'R' ? 2 : s[j] == 'D' ? 0 : s[j] == 'U' ? 1 : -1);
				if(!~num[i][j]) tar[i][j] = ++K;
				else tar[i][j] = 0;
			}
		}
		bool flg = 1;
		for(int i = 1; i <= n && flg; ++i)
			for(int j = 1, u, v; j <= m && flg; ++j)
				if(~num[i][j] && (u=i+dx[num[i][j]]) >= 1 && u <= n && (v=j+dy[num[i][j]]) >= 1 && v <= m && ~num[u][v]) {
					if(find((i-1)*m+j) == find((u-1)*m+v)) flg = 0;
					else fa[find((u-1)*m+v)] = find((i-1)*m+j);
				}
		if(!flg) { puts("0"); continue; }
		++K;
		memset(a, 0, sizeof a);
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= m; ++j)
				if(!~num[i][j])
					for(int k = 0; k < 4; ++k)
						link(dfs(i+dx[k], j+dy[k]), tar[i][j]);
		int ans = det(K-1);
		printf("%d\n", (ans + mod) % mod);
	}
}

C Duff in Mafia

在這裏插入圖片描述
題解:
二分答案+2-sat。

二分最大權值後,權值大於的邊強制不選。然後剩下的限制就是選出的邊不能有公共點,然後同一種顏色的邊不能有公共點。樸素2-sat建圖是O(m2)O(m^2)的邊數,所以直接用增加點表示前綴是否選了,邊數就是O(m)O(m)的了。

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
const int MAXN = 50005;
const int MAXM = MAXN*10;
struct edge { int u, v, c, t; }e[MAXN];
vector<int>g[MAXM], pt[MAXN];
int n, m, num, bin[MAXN], tot;

int dfn[MAXM], low[MAXM], stk[MAXM], scc[MAXM], cnt, tmr, indx;
void tar(int u) {
	dfn[u] = low[u] = ++tmr; stk[++indx] = u;
	for(int v, i = (int)g[u].size()-1; ~i; --i) {
		if(!dfn[v=g[u][i]]) tar(v), low[u] = min(low[u], low[v]);
		else if(!scc[v]) low[u] = min(low[u], dfn[v]);
	}
	if(dfn[u] == low[u]) {
		++cnt;
		do scc[stk[indx]] = cnt;
		while(stk[indx--] != u);
	}
}
bool chk(int mid) {
	for(int i = 1; i <= m; ++i) if(e[i].t > mid) g[i].pb(i+m);
	memset(dfn, 0, (num+1)<<2); tmr = 0;
	memset(scc, 0, (num+1)<<2); cnt = 0;
	for(int i = 1; i <= num; ++i) if(!dfn[i]) tar(i);
	bool re = 1;
	for(int i = 1; i <= m; ++i) if(scc[i] == scc[i+m]) { re = 0; break; }
	for(int i = 1; i <= m; ++i) if(e[i].t > mid) g[i].pop_back();
	return re;
}
inline bool cmp(int i, int j) { return e[i].c < e[j].c; }
inline void link(int u, int v, int flg) { if(flg) swap(u, v); g[u].pb(v); }
void work(const vector<int> &vec, int flg) {
	int S, T;
	for(int i = 0; i < vec.size(); ++i) {
		int x = vec[i], y = vec[i] + m, s = ++num, t = ++num;
		link(x, s, flg), link(t, y, flg);
		if(i) {
			link(x, T, flg), link(S, y, flg);
			link(S, s, flg), link(t, T, flg);
		}
		S = s, T = t;
	}
}
vector<int>S, ans;
int main () {
	scanf("%d%d", &n, &m); num = m<<1;
	for(int i = 1; i <= m; ++i) {
		scanf("%d%d%d%d", &e[i].u, &e[i].v, &e[i].c, &e[i].t);
		pt[e[i].u].pb(i), pt[e[i].v].pb(i);
		bin[++tot] = e[i].t;
	}
	for(int i = 1; i <= n; ++i) {
		sort(pt[i].begin(), pt[i].end(), cmp);
		work(pt[i], 0);
		for(int l = 0, r = 0; l < pt[i].size(); l = ++r) {
			S.pb(pt[i][l]);
			for(; r+1 < pt[i].size() && e[pt[i][r+1]].c == e[pt[i][r]].c;) S.pb(pt[i][++r]);
			work(S, 1); S.clear();
		}
	}
	sort(bin + 1, bin + tot + 1);
	tot = unique(bin + 1, bin + tot + 1) - bin - 1;
	if(!chk(bin[tot])) return puts("No"), 0;
	int l = 0, r = tot, mid;
	while(l < r) {
		mid = (l + r) >> 1;
		if(chk(bin[mid])) r = mid;
		else l = mid+1;
	}
	chk(bin[l]);
	puts("Yes");
	for(int i = 1; i <= m; ++i)
		if(scc[i] < scc[i+m]) ans.pb(i);
	printf("%d %d\n", bin[l], (int)ans.size());
	for(int i = 0; i < ans.size(); ++i)
		printf("%d ", ans[i]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章