P9167 [省选联考 2023] 城市建造 题解
-
在原图上删边,最后若满足条件,当且仅当连通块大小满足条件且每个连通块各出恰好一个节点有删边。由此推论:任意一条被删边的两个端点最后不连通。
-
进一步得到,一个点双(因为由其定义,除了只有一条边之外,都满足任意两点之间至少有两条不在点处相交的路径)内所有边要么同时删除要么同时不删除。
-
以上构成了一个方案合法的充要条件。
圆方树,方点点权为
断言:若
,直接随便删掉它到重儿子的边就是可行的。
因为若这条边不删,那么至少就有一个
由于删的边必须联通,所以每次只需要考虑新出现的连通块的根。把连通块按照重儿子大小排序,这样不断切割,并维护是否全部相等,显然等价于最大值等于最小值。注意每次删边要删掉这条边上的方点的所有边。这种情况方案数显然很小。
若
-
如果每个儿子都只有一个儿子(即每个方点对应点双大小为
)就随便保留一个儿子其它全部删除。 -
否则随便删除一个,没有其它区别。
理解:首先切出来的所有连通块在被切分前
的分布是一致的;保留后,肯定恰好有一个连通块是根为圆点,只有一个方点作为儿子。它肯定比切出的其它连通块先处理,最终得到的情形都是一样的。
这时为了避免儿子全部被删完的细节,考虑对于最小连通块大小为
另外一个细节是,在
用桶记录连通块大小,复杂度就是
这份代码写成 Shift Mountain 了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 400005, mod = 998244353;
long long ans, tmp = 1;
int n0, n, m, k;
vector<int> e[maxn];
void add(int x, int y) {
e[x].push_back(y), e[y].push_back(x);
}
// Original
int tot = 1, hd[maxn];
namespace Org {
struct Edg {
int y, nxt;
} e[maxn * 2];
int id, dfn[maxn], low[maxn], b[maxn], num;
int stk[maxn], top;
void tarjan(int x) {
low[x] = dfn[x] = ++num, stk[++top] = x;
for (int i = hd[x]; i; i = e[i].nxt) {
int y = e[i].y;
if (dfn[y]) low[x] = min(low[x], dfn[y]);
else {
tarjan(y), low[x] = min(low[x], low[y]);
if (low[y] == dfn[x]) {
++id, add(id, x);
for (int z = 0; z != y; --top)
z = stk[top], add(id, z);
}
}
}
}
void solve() {
id = n0;
for (int x = 1; x <= n0; ++x)
if (!dfn[x]) tarjan(x), --top;
n = id;
// for (int x = 1; x <= n; ++x)
// for (int y : ::e[x])
// printf("%d %d\n", x, y);
}
}
// New
int rt, ms[maxn], siz[maxn], f[maxn];
void init(int x, int fa) {
siz[x] = (x <= n0);
for (int y : e[x])
if (y != fa) init(y, x), siz[x] += siz[y], ms[x] = max(ms[x], siz[y]);
ms[x] = max(ms[x], n0 - siz[x]);
if (x <= n0 && ms[rt] > ms[x]) rt = x;
}
void dfs(int x, int fa) {
f[x] = fa, siz[x] = (x <= n0), ms[x] = n + 1;
for (int y : e[x])
if (y != fa) dfs(y, x), ms[x] = min(ms[x], siz[y]), siz[x] += siz[y];
}
vector<int> q[maxn];
int mx, mi, b[maxn];
void insert(int x) {
assert(x > n0), b[x] = 1;
assert(f[x]), siz[f[x]] -= siz[x];
for (int y : e[x])
if (y != f[x]) q[siz[y]].push_back(y), mi = min(mi, siz[y]);//, cout << y << " " << siz[y] << endl;
}
// 暴力
//namespace Patch {
// int ans;
// int b[maxn], t, siz, mx, mi;
// void dfs(int x) {
// b[x] = 1, siz += (x <= n0);
// for (int y : e[x])
// if (!b[y] && (y <= n0 || !(t & (1 << (y - n0 - 1))))) dfs(y);
// }
// void check(int x) {
// b[x] = 1;
// for (int y : e[x])
// if (!b[y] && (y <= n0 || (t & (1 << (y - n0 - 1))))) check(y);
// }
// int solve() {
// if (n - n0 > 20) return 0;
// int lim = (1 << (n - n0));
// for (t = 1; t < lim; ++t) {
// mi = 0x3f3f3f3f, mx = 0, tmp = 0;
// for (int i = 1; i <= n; ++i) b[i] = 0;
// for (int i = n0 + 1; i <= n; ++i)
// if ((t & (1 << (i - n0 - 1))) && !b[i]) ++tmp, check(i);
// if (tmp > 1) continue;
// for (int i = 1; i <= n; ++i) b[i] = 0;
// for (int i = 1; i <= n0; ++i)
// if (!b[i]) siz = 0, dfs(i), mx = max(mx, siz), mi = min(mi, siz);
// if ((mx - mi <= k)) ++ans;
// //for(int i = n0 + 1; i <= n; ++i) if (t & (1 << (i - n0 - 1))) cout << i << " "; cout << endl;
// }
// return ans;
// }
//}
long long dp[maxn];
bool leaf[maxn];
void solve(int x, int fa) {
b[x] = 1, dp[x] = 1;
leaf[x] = e[x].size() == 1;
int ct = 0;
for (int y : e[x]) {
if (b[y] || y == fa) continue;
solve(y, x), leaf[x] |= (x > n0 && leaf[y] && e[x].size() <= 2);
if (x > n0) (dp[x] *= dp[y]) %= mod;
else if (leaf[y] == 1) assert(y > n0), ++ct;
else assert(y > n0), dp[x] = dp[x] * dp[y] % mod;
}
dp[x] = dp[x] * (ct + 1) % mod;
if (dp[x] < 1) exit(0);
}
signed main() {
int x, y, z;
scanf("%d%d%d", &n0, &m, &k);
for (int i = 1; i <= m; ++i) {
scanf("%d%d", &x, &y);
Org::e[++tot] = {y, hd[x]}, hd[x] = tot;
Org::e[++tot] = {x, hd[y]}, hd[y] = tot;
}
Org::solve(), ms[rt = 0] = n + 1, init(1, 0), dfs(rt, 0);
q[siz[rt]].push_back(rt), mx = mi = siz[rt];
while (mx > 1) {
x = q[mx].back(), q[mx].pop_back();
int sx = 0, si = 0, sn = 0, ns = 0;
for (int y : e[x]) {
if (b[y] || y == f[x]) continue;
++sn, ns = max(ns, int(e[y].size()) - 1);
if (!sx || siz[y] > siz[sx] || (siz[y] == siz[sx] && ms[sx] < ms[y])) sx = y;
if (!si || siz[y] < siz[si]) si = y;
}
assert(x <= n && sx && si);
if (k == 0 || sn == 1 || siz[sx] > siz[si] || ns > 1) {
insert(sx);
q[siz[x]].push_back(x), mi = min(mi, siz[x]);
} else {
assert(k == 1 && sn > 1 && siz[sx] == siz[si]);
for (int y : e[x])
if (!b[y] && y != f[x] && y != sx) insert(y);
q[siz[x]].push_back(x), mi = min(mi, siz[x]);
(tmp *= sn) %= mod;
}
if (mi <= 1) break;
while (q[mx].empty() && mx > 1) --mx;
if (mx - mi <= k) (ans += tmp) %= mod;
}
if (k == 0) {
printf("%lld", (ans + 1) % mod);
return 0;
}
memset(b, 0, sizeof(b));
solve(rt, 0), ans += dp[rt] % mod;
// printf("%d\n", Patch::solve());
printf("%lld", ans % mod);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话