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

浙公网安备 33010602011771号