题解: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;
}

浙公网安备 33010602011771号