题解:P15403 [NOISG 2026 Prelim] Mushroom Ring

不是很难。

考虑固定终点 \(d\),如果一条有向边 \(u\rightarrow v\) 的路标包含 \(d\),则这条有向边是可以走的。显然每个点的出度至多为 \(1\),因此每个连通分量必然是一棵内向树或一棵内向基环树。而 \(d\) 的出度恰好为 \(0\),因此固定终点为 \(d\) 时,合法的 \(s\) 的个数就是以 \(d\) 为根的内向树的大小。

那么我们就要通过编辑操作来增加以 \(d\) 为根的内向树的大小。讨论连通分量的类型:

  • 内向树:设树根为 \(rt\),树的大小为 \(sz\)。可以在原图中 \(rt\rightarrow d\) 对应的路标添加 \(d\),这样就添加了一条 \(rt\rightarrow d\) 的有向边。这样花费 \(1\) 次操作产生了 \(sz\) 的贡献。
  • 内向基环树:设树的大小为 \(sz\)。此时只能选取一条环上的边 \(u\rightarrow v\),将 \(d\) 从路标中删去,再往 \(u\rightarrow d\) 对应的路标添加 \(d\)。这样花费 \(2\) 次操作产生了 \(sz\) 的贡献。

显然不同的 \(d\) 互不影响,不妨对于所有的 \(d\),求出 \(cnt_1(x)\) 表示总共有多少 \(sz=x\) 的内向树,\(cnt_2(x)\) 表示总共有多少 \(sz=x\) 的内向基环树。这样问题就转化成了,对于每个 \(1\leq i\leq n\),有 \(cnt_1(i)\) 个体积为 \(1\)、价值为 \(i\) 的物品,有 \(cnt_2(i)\) 个体积为 \(2\)、价值为 \(i\) 的物品。求容量为 \(k\) 的背包能装下的最大价值。若 \(k\) 为奇数,则体积为 \(1\) 的最大价值的物品必选。于是转化为 \(k\) 为偶数的情况,此时体积为 \(1\) 的物品按价值从大到小排序后可以两两配对构成体积为 \(2\) 的物品。做一些简单的讨论即可 \(\mathcal{O}(n)\) 解决。

考虑如何求出 \(cnt_1,cnt_2\)。显然一条边 \(u_i\rightarrow v_i\) 只会在终点 \(d\) 满足 \(a_i\leq d\leq b_i\) 时出现。容易想到线段树分治,这样只有加边和撤销,进入叶子节点 \(d\) 时就是终点固定为 \(d\) 时得到的图。用可撤销并查集维护,在连通分量的根节点上记录大小和边数。在一个连通分量的大小或边数发生改变时,使用经典技巧:设当前已经处理了 \(t\) 个叶子节点,那么我们假定这个 \(sz\)\(t+1\sim n\) 这些叶子都有贡献,在更改时对应加减贡献即可。注意进入叶子节点时要把 \(d\) 所在内向树的贡献加到答案中。

时间复杂度为 \(\mathcal{O}(n+m\log^2{n})\)

代码并不难写。

主要代码
struct DSU {
	int fa[MAXN], sz[MAXN], edges[MAXN];
	int top;
	pii stk[MAXM];
	void init() {
		for (int i = 1; i <= n; ++i) {
			fa[i] = i;
			sz[i] = 1;
		}
	}
	int find(int x) { return x == fa[x] ? x : find(fa[x]); }
	void upd(int x, int c) {
		if (edges[x] == sz[x] - 1) cnt1[sz[x]] += (n - cur) * c;
		else cnt2[sz[x]] += (n - cur) * c;
	}
	void unite(int x, int y) {
		x = find(x), y = find(y);
		if (x == y) {
			upd(x, -1);
			++edges[x];
			stk[++top] = {x, x};
			upd(x, 1);
			return;
		}
		if (sz[x] > sz[y]) swap(x, y);
		upd(x, -1);
		upd(y, -1);
		fa[x] = y;
		sz[y] += sz[x];
		edges[y] += edges[x] + 1;
		stk[++top] = {x, y};
		upd(y, 1);
	}
	void undo(int t) {
		while (top > t) {
			auto [x, y] = stk[top--];
			if (x == y) {
				upd(x, -1);
				--edges[x];
				upd(x, 1);
			} else {
				upd(y, -1);
				fa[x] = x;
				sz[y] -= sz[x];
				edges[y] -= edges[x] + 1;
				upd(x, 1);
				upd(y, 1);
			}
		}
	}
} dsu;

struct SegTree {
#define ls(p) (p << 1)
#define rs(p) (p << 1 | 1)
	vector<pii> edges[MAXN << 2];
	void ins(int p, int l, int r, int x, int y, pii e) {
		if (x <= l && y >= r) {
			edges[p].emplace_back(e);
			return;
		}
		int mid = l + r >> 1;
		if (x <= mid) ins(ls(p), l, mid, x, y, e);
		if (y > mid) ins(rs(p), mid + 1, r, x, y, e);
	}
	void solve(int p, int l, int r) {
		int t = dsu.top;
		for (auto [u, v] : edges[p]) dsu.unite(u, v);
		if (l == r) {
			++cur;
			int sz = dsu.sz[dsu.find(l)];
			--cnt1[sz];
			ans += sz;
		} else {
			int mid = l + r >> 1;
			solve(ls(p), l, mid);
			solve(rs(p), mid + 1, r);
		}
		dsu.undo(t);
	}
#undef ls
#undef rs
} sgt;

int main() {
	ios::sync_with_stdio(0), cin.tie(0);
	rd(n, m, k);
	while (m--) {
		int u, v, a, b;
		rd(u, v, a, b);
		sgt.ins(1, 1, n, a, b, {u, v});
	}
	dsu.init();
	cnt1[1] = (ll)n * n;
	sgt.solve(1, 1, n);
	if (k & 1) {
		for (int i = n; i; --i) {
			if (!cnt1[i]) continue;
			ans += i;
			--cnt1[i];
			--k;
			break;
		}
	}
	int c = 0;
	for (int i = n; i; --i) {
		if (!cnt1[i]) continue;
		if (c) {
			++cnt2[c + i];
			--cnt1[i];
			c = 0;
		}
		cnt2[i << 1] += cnt1[i] >> 1;
		if (cnt1[i] & 1) c = i;
	}
	int ex = 0;
	if (c) ex = c;
	c = 0;
	for (int i = n << 1; i && k >= 2; --i) {
		if (!cnt2[i]) continue;
		if (c) {
			k -= 2;
			ans += c + i;
			--cnt2[i];
			c = 0;
		}
		ll t = min(cnt2[i], k >> 1);
		ans += t * i;
		cnt2[i] -= t;
		k -= t << 1;
		if (cnt2[i]) c = i;
	}
	if (ex && k) ans += ex;
	cout << ans;
	return 0;
}
posted @ 2026-05-03 16:43  P2441M  阅读(18)  评论(0)    收藏  举报