算法
首先, 合法路径上至少有两条边颜色不同, 我们考虑正难则反, 统计不合法的数量, 那么合法数量就等于总可能数 \(-\) 不可能数
中间转化的部分在这里写没有什么意义, 跳转至 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);
}
总结
正难则反
善于转化问题, 善于利用数据结构