P9167 [省选联考 2023] 城市建造 题解

  1. 在原图上删边,最后若满足条件,当且仅当连通块大小满足条件且每个连通块各出恰好一个节点有删边。由此推论:任意一条被删边的两个端点最后不连通。

  2. 进一步得到,一个点双(因为由其定义,除了只有一条边之外,都满足任意两点之间至少有两条不在点处相交的路径)内所有边要么同时删除要么同时不删除。

  3. 以上构成了一个方案合法的充要条件。


圆方树,方点点权为 0。如果能找到一个点周围的边必须删,以其为根这个问题就很简单。可以证明重心是这样的点。

断言:若 k=0,直接随便删掉它到重儿子的边就是可行的。

因为若这条边不删,那么至少就有一个 sizsonx+1 的大小,而他是重心,所以得到 nsizsonxsizsonx<sizsonx+1。那么其他部分不可能和这个相等。

由于删的边必须联通,所以每次只需要考虑新出现的连通块的根。把连通块按照重儿子大小排序,这样不断切割,并维护是否全部相等,显然等价于最大值等于最小值。注意每次删边要删掉这条边上的方点的所有边。这种情况方案数显然很小。

k=1,沿用先前,发现这时候多出一种情况,把根和重子树划分在一起,再删掉一些边使得这些子树相等。那么那些子树的大小必须等于重子树,于是所有子树必须都相等。首先一开始钦定当前的根不为方点(可能方点是重心,但是这样的话第一下就删除了所以无所谓),所以进而要求:

  1. 如果每个儿子都只有一个儿子(即每个方点对应点双大小为 2)就随便保留一个儿子其它全部删除。

  2. 否则随便删除一个,没有其它区别。

理解:首先切出来的所有连通块在被切分前 siz 的分布是一致的;保留后,肯定恰好有一个连通块是根为圆点,只有一个方点作为儿子。它肯定比切出的其它连通块先处理,最终得到的情形都是一样的。

这时为了避免儿子全部被删完的细节,考虑对于最小连通块大小为 1 的情况特判处理。

另外一个细节是,在 k=1 时,若最大儿子存在多个,应该对删除后形成的最小连通块最大的优先删除。

用桶记录连通块大小,复杂度就是 O(n+m)

这份代码写成 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;
}
posted @   音街ウナ  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示