P9036 「KDOI-04」挑战 NPC Ⅲ 题解

Description

给出一个含有 nn 个顶点,mm 条边的无向图 GG,求 GG 中大小恰好为 nkn-k 的独立集的数量。由于答案可能很大,请将其对 998 244 353998~244~353 取模。

1n1051\leq n\leq10^50m1050\le m\le 10^50kmin(n1,18)0\leq k\leq \min(n-1,18)1T1041\leq T\leq 10^{4}n,m106\sum n,\sum m\leq10^6

Solution

注意到 kk 很小,取独立集的补集则可将问题转化为求有多少个大小为 kk 的点覆盖。

这类问题有个很普遍的技巧是考虑删点,这题就是如果存在一个点度数大于 kk,则这个点一定会选,否则需要选所有它邻域的点就超了。在删点的同时把边也删掉。

如果剩下的边数超过 k2k^2 则一定无解,因为剩下的点度数都不超过 kk,选 kk 个最多只能覆盖 k2k^2 条边。

那么问题转化为在一个 O(k2)O(k^2) 个点和边的图上做这个问题。

仍然考虑删点,任意拿出一个点,分讨它选不选。如果选,则把这个点和与这个点相连的边删掉。如果不选,则这个点邻域没被删掉的所有点都要选,这些邻域的点和边也能删掉。

那么复杂度为 T(k)=T(k1)+T(kdegx1)+O(k2)T(k)=T(k-1)+T(k-deg_x-1)+O(k^2),容易发现每次选度数最大的一定最优。如果最大的度数小于等于 22 就能停止了,因为剩下的一定是一些环和一些链,dp 即可。

现在复杂度为 T(k)=T(k1)+T(k3)+O(k2)T(k)=T(k-1)+T(k-3)+O(k^2),能轻松跑过 k=18k=18

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 1e5 + 5, kMod = 998244353;

int n, m, k, cnt, ans;
int u[kMaxN], v[kMaxN], deg[kMaxN], pos[kMaxN], id[605], g[605][605];
int ff[605][25], gg[605][25], hh[25];
bool del[kMaxN], exi[kMaxN];

// ff[i][j] : 长度为 i 的链,选 j 个点覆盖方案数
// gg[i][j] : 长度为 i 的环,选 j 个点覆盖方案数

int qpow(int bs, int64_t idx = kMod - 2) {
int ret = 1;
for (; idx; idx >>= 1, bs = (int64_t)bs * bs % kMod)
if (idx & 1)
ret = (int64_t)ret * bs % kMod;
return ret;
}

inline int add(int x, int y) { return (x + y >= kMod ? x + y - kMod : x + y); }
inline int sub(int x, int y) { return (x >= y ? x - y : x - y + kMod); }
inline void inc(int &x, int y) { (x += y) >= kMod ? x -= kMod : x; }
inline void dec(int &x, int y) { (x -= y) < 0 ? x += kMod : x; }

void prework(int n = 600, int k = 18) {
static int tmp[605][25] = {0};
memset(tmp, 0, sizeof(tmp));
tmp[0][0] = 1;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j <= k; ++j) {
// i 选
if (j) {
inc(ff[i][j], tmp[i - 1][j - 1]);
if (i >= 2) inc(ff[i][j], tmp[i - 2][j - 1]);
}
tmp[i][j] = ff[i][j];
// i 不选
inc(ff[i][j], tmp[i - 1][j]);
}
}
for (int op = 0; op < 2; ++op) {
memset(tmp, 0, sizeof(tmp));
if (!op) tmp[0][0] = 1;
else tmp[1][1] = 1;
gg[1][op] = 1;
for (int i = 2; i <= n; ++i) {
for (int j = 0; j <= k; ++j) {
// i 选
if (j) {
inc(tmp[i][j], tmp[i - 1][j - 1]);
if (i >= 2) inc(tmp[i][j], tmp[i - 2][j - 1]);
}
inc(gg[i][j], tmp[i][j]);
// i 不选
if (op) inc(gg[i][j], tmp[i - 1][j]);
}
}
}
}

void upd() {
assert(k >= 0);
static int fa[605], sz[605], cnte[605];
for (int i = 1; i <= cnt; ++i) fa[i] = i, sz[i] = 1, cnte[i] = 0;
std::function<int(int)> find = [&] (int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); };
std::function<void(int, int)> unionn = [&] (int x, int y) {
int fx = find(x), fy = find(y);
if (fx != fy) fa[fx] = fy, sz[fy] += sz[fx], cnte[fy] += cnte[fx];
++cnte[fy];
};
for (int i = 1; i <= cnt; ++i) {
if (del[id[i]]) continue;
for (int j = i + 1; j <= cnt; ++j)
if (!del[id[j]] && g[i][j])
unionn(i, j);
}
static int f[25], tmp[25];
memset(f, 0, sizeof(f));
f[0] = 1;
int tc = 0;
for (int i = 1; i <= cnt; ++i) {
if (!del[id[i]] && i == find(i)) {
tc += sz[i];
for (int j = 0; j <= k; ++j) tmp[j] = f[j], f[j] = 0;
if (cnte[i] == sz[i] - 1) {
for (int j = 0; j <= k; ++j)
for (int s = 0; s <= k - j; ++s)
inc(f[j + s], 1ll * tmp[j] * ff[sz[i]][s] % kMod);
} else {
for (int j = 0; j <= k; ++j)
for (int s = 0; s <= k - j; ++s)
inc(f[j + s], 1ll * tmp[j] * gg[sz[i]][s] % kMod);
}
}
}
for (int i = 0; i <= k; ++i) inc(ans, 1ll * f[i] * hh[k - i] % kMod);
}

void delet(int x) {
assert(pos[x]);
del[x] = 1;
for (int i = 1; i <= cnt; ++i)
if (!del[id[i]] && g[pos[x]][i])
--deg[x], --deg[id[i]];
}

void rollback(int x) {
assert(pos[x]);
del[x] = 0;
for (int i = 1; i <= cnt; ++i)
if (!del[id[i]] && g[pos[x]][i])
++deg[x], ++deg[id[i]];
}

void dfs() {
if (k < 0) return;
int x = 0;
for (int i = 1; i <= cnt; ++i) {
if (!del[id[i]] && deg[id[i]] > deg[x]) x = id[i];
}
if (!x || deg[x] <= 2) return upd();
delet(x), --k, dfs(), ++k, rollback(x);

std::vector<int> vid;
del[x] = 1;
for (int i = 1; i <= cnt; ++i)
if (!del[id[i]] && g[pos[x]][i])
--k, delet(id[i]), vid.emplace_back(i);
dfs();
for (auto i : vid) ++k, rollback(id[i]);
del[x] = 0;
}

void dickdreamer() {
static std::unordered_map<int, bool> mp[kMaxN];
std::cin >> n >> m >> k;
for (int i = 1; i <= n; ++i) deg[i] = del[i] = pos[i] = 0, mp[i].clear();
for (int i = 1; i <= m; ++i) {
std::cin >> u[i] >> v[i];
if (!mp[u[i]].count(v[i])) ++deg[u[i]], ++deg[v[i]], mp[u[i]][v[i]] = mp[v[i]][u[i]] = exi[i] = 1;
else exi[i] = 0;
}
int now = 0;
for (int i = 1; i <= n; ++i)
if (deg[i] > k)
del[i] = 1, ++now;
if (now > k) return void(std::cout << "0\n");
int cnte = 0;
std::vector<int> vec;
std::fill_n(deg + 1, n, 0);
for (int i = 1; i <= m; ++i) {
if (exi[i] && !del[u[i]] && !del[v[i]]) {
++cnte, vec.emplace_back(u[i]), vec.emplace_back(v[i]);
}
}
if (cnte > k * k) return void(std::cout << "0\n");
k -= now;
std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
cnt = vec.size();
for (int i = 1; i <= cnt; ++i) {
pos[id[i] = vec[i - 1]] = i, deg[id[i]] = 0;
for (int j = 1; j <= cnt; ++j) g[i][j] = 0;
}
for (int i = 1; i <= m; ++i) {
if (!del[u[i]] && !del[v[i]]) {
g[pos[u[i]]][pos[v[i]]] = g[pos[v[i]]][pos[u[i]]] = 1;
}
}
for (int i = 1; i <= cnt; ++i) {
deg[id[i]] = 0;
for (int j = 1; j <= cnt; ++j)
deg[id[i]] += g[i][j];
}
int noww = 0;
for (int i = 1; i <= n; ++i)
if (!del[i] && !pos[i])
++noww;
for (int i = 0; i <= k; ++i) {
int cnt = 1;
for (int j = noww; j > noww - i; --j) cnt = 1ll * cnt * j % kMod;
for (int j = 1; j <= i; ++j) cnt = 1ll * cnt * qpow(j) % kMod;
hh[i] = cnt;
}
ans = 0, dfs();
std::cout << ans << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
std::cin >> T; prework();
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

P9036 「KDOI-04」挑战 NPC Ⅲ 题解
https://sobaliuziao.github.io/2025/08/26/post/b44e4d87.html
作者
Egg_laying_master
发布于
2025年8月26日
许可协议