P4426 [HNOI/AHOI2018] 毒瘤
TernaryTree · · 题解
非常好广义串并联图。虚树/no
根据经典结论,通过以下三个操作,最终得到的图的边数是
- 消串联:选中一个度数为
2 的点u ,设其连接的两个点分别为v_1,v_2 ,将(u,v_1)(u,v_2) 删掉,再连接(v_1,v_2) 。 - 消并联:选中一对重边并删掉一条。
- 去断路:选中一个度数为
1 的点u ,将u 与u 所连的唯一一条边删掉。
那么在这个题里面,可以发现
在哪里维护答案呢?比较显然的是在边上的情况。对于一条边
- 选
u 选v ,(u,v) 表示子图的独立集方案数 - 不选
u 不选v ,(u,v) 表示子图的独立集方案数 - 不选
u 选v ,(u,v) 表示子图的独立集方案数 - 选
u 不选v ,(u,v) 表示子图的独立集方案数
对于“消串联”操作,我们发现可以直接枚举
对于“消并联”操作,我们发现实际上两条边的对应权值相互独立,于是四个值对应相乘即可。
问题来到了“去断路”操作,我们发现没办法直接统计答案,坏诶。于是考虑去给每个点一个权值,缩一个断路就相当于把这个断路表示的子图的贡献挂在了这个结点上。于是每个结点就有两个权值:
那么这个时候,“去断路“操作就可以手推一下,本质是将
这里还要注意一点,此时的“消串联”操作也发生了变化,因为中间点选还是不选又乘上了一个该点自己的系数,稍微修改一下转移即可。
统计答案直接枚举哪些点选了,将点的对应权值与边的对应权值乘起来,再累加到答案即可。
#include <bits/stdc++.h>
#define int long long
#define umap unordered_map
using namespace std;
typedef pair<int, int> pii;
const int maxn = 2e6 + 10;
const int mod = 998244353;
struct edge {
int u, v;
int w[5] = {};
edge() = default;
edge(int u, int v, int a, int b, int c, int d): u(u), v(v) { w[1] = a, w[2] = b, w[3] = c, w[4] = d; }
int& operator[] (int x) { return w[x]; }
};
int n, m, ans;
umap<int, edge> e[maxn];
edge ed[maxn];
int tot;
int deg[maxn];
int f[maxn], g[maxn];
int del[maxn];
int que[maxn], hd = 1, tl;
int id[maxn], cnt;
int pos[maxn];
void add_edge(int u, int v, int a, int b, int c, int d) {
if (e[u].count(v)) {
(e[u][v][1] *= a) %= mod;
(e[u][v][2] *= b) %= mod;
(e[u][v][3] *= c) %= mod;
(e[u][v][4] *= d) %= mod;
} else {
edge cur = edge(u, v, a, b, c, d);
e[u][v] = cur;
deg[u]++;
}
}
void del_edge(int u, int v) {
e[u].erase(v);
deg[u]--;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++) {
cin >> u >> v;
add_edge(u, v, 0, 1, 1, 1);
add_edge(v, u, 0, 1, 1, 1);
}
for (int i = 1; i <= n; i++) f[i] = g[i] = 1;
for (int i = 1; i <= n; i++) if (deg[i] <= 2) que[++tl] = i;
while (hd <= tl) {
int u = que[hd++];
if (!deg[u]) continue;
if (deg[u] == 1) {
edge i = e[u].begin()->second;
int v = i.v;
(f[v] *= (f[u] * i[1] % mod + g[u] * i[4] % mod) % mod) %= mod;
(g[v] *= (f[u] * i[3] % mod + g[u] * i[2] % mod) % mod) %= mod;
del[u] = true;
del_edge(u, v), del_edge(v, u);
if (deg[v] <= 2) que[++tl] = v;
} else {
edge i = e[u].begin()->second;
edge j = next(e[u].begin())->second;
int v1 = i.v, v2 = j.v;
swap(i[3], i[4]);
edge cur = edge(v1, v2,
(i[1] * j[1] % mod * f[u] % mod + i[3] * j[4] % mod * g[u] % mod) % mod,
(i[4] * j[3] % mod * f[u] % mod + i[2] * j[2] % mod * g[u] % mod) % mod,
(i[1] * j[3] % mod * f[u] % mod + i[3] * j[2] % mod * g[u] % mod) % mod,
(i[4] * j[1] % mod * f[u] % mod + i[2] * j[4] % mod * g[u] % mod) % mod
);
del[u] = true;
del_edge(u, v1), del_edge(v1, u);
del_edge(u, v2), del_edge(v2, u);
add_edge(cur.u, cur.v, cur[1], cur[2], cur[3], cur[4]);
add_edge(cur.v, cur.u, cur[1], cur[2], cur[4], cur[3]);
if (deg[v1] <= 2) que[++tl] = v1;
if (deg[v2] <= 2) que[++tl] = v2;
}
}
for (int i = 1; i <= n; i++) {
if (!del[i]) {
id[++cnt] = i;
pos[i] = cnt;
}
}
for (int u = 1; u <= n; u++) {
if (del[u]) continue;
for (pair<int, edge> i : e[u]) if (u < i.second.v) ed[++tot] = i.second;
}
for (int S = 0; S < (1ll << cnt); S++) {
int mul = 1;
for (int i = 1; i <= cnt; i++) {
if (S >> i - 1 & 1) (mul *= f[id[i]]) %= mod;
else (mul *= g[id[i]]) %= mod;
}
for (int i = 1; i <= tot; i++) {
int u = pos[ed[i].u], v = pos[ed[i].v];
int p = S >> u - 1 & 1, q = S >> v - 1 & 1;
if (p && q) (mul *= ed[i][1]) %= mod;
else if (!p && !q) (mul *= ed[i][2]) %= mod;
else if (p && !q) (mul *= ed[i][3]) %= mod;
else (mul *= ed[i][4]) %= mod;
}
(ans += mul) %= mod;
}
cout << ans << endl;
return 0;
}