演算法
首先, 合法路徑上至少有兩條邊顏色不同, 我們考慮正難則反, 統計不合法的數量, 那麼合法數量就等於總可能數 \(-\) 不可能數
中間轉化的部分在這裡寫沒有什麼意義, 跳轉至 Luogu 題解區
那麼我們的問題轉化成, 如何計算多條路徑中, 某些路徑不合法的情況數
首先我們需要考慮, 對於每條路徑, 我們考慮將其縮成一個點, 方便計算, 因為只要選擇了這一個點的顏色, 其他的都確定了 (這裡是不合法情況)
這個我們用並查集處理, 在找每條路徑時, 記錄一下即可
在列舉第 \(i\) 條路徑顏色相同並累加上對應情況數時,我們使其它邊顏色任選,就會出現其它指定的路徑顏色也相同的情況,也就是說,在計算第 \(i\) 種情況時,也會算上一部分 \(j\) 的情況( \(W_i\) 和 \(W_j\) 有交集),或者說,\(W_i\) 實際上是 至少 第 \(i\) 條路徑顏色相同的情況
關於如何處理路徑不合法的方案數重複計算這一問題, 我們考慮使用容斥原理
使用狀壓的 \(\rm{trick}\)
程式碼
#include <bits/stdc++.h>
#define int long long
const int MOD = 1e9 + 7;
const int MAXN = 65;
int n, m, k;
int kpow[MAXN];
int Ans = 0;
std::map<std::pair<int, int>, int> mp;
std::vector<int> e[MAXN], Past[MAXN];
struct DSU_struct
{
int fa[MAXN];
int getfa(int x) { return fa[x] == x ? x : fa[x] = getfa(fa[x]); }
void fa_init() { for (int i = 1; i <= n; i++) fa[i] = i; }
} DSU;
bool dfs(int Now, int to, int fa, int num)
{
if (Now == to)
return true;
for (auto i : e[Now])
{
if (i == fa)
continue;
if (dfs(i, to, Now, num))
{
Past[num].push_back(mp[{Now, i}]);
return true;
}
}
return false;
}
int calc(int x)
{
DSU.fa_init();
int cnt = 0;
for (int i = 1; i <= m; i++)
if (x & (1 << (i - 1)))
for (auto j : Past[i])
DSU.fa[DSU.getfa(j)] = DSU.getfa(Past[i][0]);
for (int i = 1; i <= n - 1; i++)
cnt += (DSU.fa[i] == i);
return cnt;
}
void init() {
kpow[0] = 1;
for (int i = 1; i <= n; i++)
kpow[i] = kpow[i - 1] * k % MOD;
}
signed main()
{
scanf("%lld %lld %lld", &n, &m, &k);
init();
for (int i = 1; i <= n - 1; i++) {
int u, v;
scanf("%lld %lld", &u, &v);
mp[{u, v}] = mp[{v, u}] = i;
e[u].push_back(v);
e[v].push_back(u);
}
/*計算簡單路徑長度*/
for (int i = 1; i <= m; i++) {
int u, v;
scanf("%d %d", &u, &v);
dfs(u, v, u, i);
}
for (int i = 0; i < (1 << m); i++) {
Ans += ((__builtin_popcount(i) & 1) ? -1 : 1) * kpow[calc(i)] + MOD;
Ans %= MOD;
}
printf("%lld", Ans);
}
總結
正難則反
善於轉化問題, 善於利用資料結構