A. Stranger Trees
題面
給定一棵有個節點的無根樹
求對於這個點,以及每個,有多少棵由這n個點構成的帶標號無根樹,與給定的樹恰好有條邊重複。
答案模。
題解
設恰好有條邊重複的方案爲,至少有條邊的方案爲。
答案要求,而
二項式反演一下就是
所以答案轉化爲求,可以通過樹形DP求出。
考慮已經確定了條邊。那麼個點被分成了個連通塊。
有一個結論,就是共有個點,個聯通塊,每個連通塊裏點數爲的生成樹方案爲那麼求這個連通塊的生成樹方案就是
後面的乘積就是每個聯通塊選一個點的方案。
咋那麼就可以直接樹形DP,表示的子樹中,確定了條邊,所在連通塊是否已經選了點的方案。轉移就是樹形DP常見的轉移套路,時間複雜度。
如果爲根DP,就等於。
然後就做完了。時間複雜度。
#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);
}
}
結論證明
考慮證明上面那個結論。
有兩種證法。一種是矩陣樹定理,由於比較麻煩且公式不好寫(太菜了)跳過。
用序列來證明:
假設有個點,個連通塊。每個連通塊大小爲
把一個連通塊縮成一個點,那麼這一棵生成樹的序列長度爲。如果連通塊的出現次數爲,那麼貢獻是。那麼我們構造每個連通塊的爲。
這道題就結束了。
B 白金元首與獨舞
把每個格子向他指向的格子連邊,將外界看作一個點,那麼合法方案是一個有根的內向樹的形態。那麼就是有向生成樹計數,但是無法直接建圖,因爲點數是的。發現,那麼就可以對於一個不確定的位置朝一個方向按照箭頭走,走到第一個不確定位置或者邊界再連邊,這樣點數是的,邊數也是,就能夠過掉此題。
#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建圖是的邊數,所以直接用增加點表示前綴是否選了,邊數就是的了。
#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]);
}