题解:AT_agc008_f [AGC008F] Black Radius
_Communist · · 题解
很好玩的题目!没有想到题解区做法,这里给一个鏖战许久的容斥做法(不知道为什么写了这么长)。
这个关键点、距离等限制有一种 [十二省联考 2019] 希望 的既视感(虽然这个题更早)。直接算单个点的贡献就是以其为根时的最大深度,直接加起来会算重。
和希望那题一样,猜测对于一个树上连通块
先考虑
考虑容斥,可以直接套点边容斥,就是只需要算点的方案数减去边的方案数。更多关于这个结论的内容可以去希望的题解区。
点的方案数上面提过了,就是以其为根时的最大深度(根的深度为
至于边的方案,考察树上一条边
令
-
若
d_v<y ,此时必须有d_u=d_v+1 ,否则在v 子树内的染色情况不同。那么这时必须要求d_v\geq x+1 ,否则在v 子树外的染色情况不同。这种情况的方案数为\max(0,y-x-1) 。 -
若
d_u<x ,此时必须有d_v=d_u+1 ,否则在v 子树外的染色情况不同。同时有d_u\geq y+1 ,否则在v 子树内的染色情况不同。这种情况的方案数为\max(0,x-y-1) ,可以看到和上面那种情况是恰好互补的。 -
若
d_u=d_v ,这是唯一剩下的没有被上面讨论到的情况。此时取d_u=d_v=\max(x,y) 是唯一解,方案数为1 。
综合一下几种情况,一条边
自此解决了
接下来考虑原题目。
原题目由于指定了关键点,容斥会有点麻烦。具体来说,不妨回归本源,钦定一些关键点
由于
将连通块上的所有点依次标号为
再设
从上面
于是可以发现对于一个连通块真正产生贡献的其实只有一个
Caution:这里需要注意的是连通块的根如果是关键点,度数必须为
于是考虑分开计算每个满足
-
对于一个在连通块上不为根的关键点
u 来说,如果其子树外存在其它关键点,其容斥系数之和为-1 ,否则为0 。这是因为假设其子树外有c 个关键点满足到u 的路径上没有其它关键点,容斥系数为\sum\limits_{i=1}^c(-1)^{i}\binom{c}{i}=-[c\not=0] 。 -
对于一个在连通块上为根的关键点来说,如果其子树内存在其它关键点,其容斥系数之和为
-1 ,否则为0 。 -
对于一个在连通块上的非关键点
u 来说——这是最麻烦的情况,因为其“子树”内外的最大距离是不确定的——先再明确一遍其“子树”的定义:如果一个点到u 的路径上经过了除u 以外的连通块上的点,这个点在u 的“子树”外,否则在u 的“子树”内。由于子树内最大距离大于子树外最大距离才会产生贡献,u 儿子(这里暂且以u 为根,就是将其原树上的父亲也视为儿子)的子树当中最大距离最大的那个一定会被划分到u 的“子树”内,而其它的儿子可以随意划分(当然只有在其子树内有关键点的时候才能被划分到u 的“子树”外)。然后再枚举“子树”外的最大距离计算贡献。假设“子树”外最大距离为d ,则考察是否存在至少两个子树内最大距离\leq d-1 的儿子有关键点,有的话容斥系数就是-1 ,否则为0 。
注意还要考虑整个连通块不存在
总结一下,这个做法的关键在于容斥之后通过计数对象的转变(从集合到树上联通块再到点的贡献),容斥系数得到了极大的(?)简化,变成了便于计算(?)的形式。
更多细节可以看代码,其实很短。
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr)
using LL = long long;
constexpr int MAXN = 2e5 + 5;
int n, mx[MAXN], smx[MAXN], out[MAXN], cnt_key[MAXN];
//子树内最大/次大,子树外最大,子树内关键点数
vector<int> G[MAXN];
LL ans;
string S;
void dfs(int u, int fa) {
cnt_key[u] += (S[u] == '1');
for (int v : G[u]) if (v != fa) {
dfs(v, u);
cnt_key[u] += cnt_key[v];
if (mx[v] + 1 > mx[u]) smx[u] = mx[u], mx[u] = mx[v] + 1;
else smx[u] = max(smx[u], mx[v] + 1);
}
}
void Get_out(int u, int fa) {
if (S[u] == '1') ans += max(mx[u], out[u]);
for (int v : G[u]) if (v != fa) {
out[v] = max(out[u], mx[v] + 1 == mx[u] ? smx[u] : mx[u]) + 1;
Get_out(v, u);
}
}
void Get_ans(int u, int fa) {
for (int v : G[u]) if (v != fa) Get_ans(v, u);
if (S[u] == '1') {
if (cnt_key[1] - cnt_key[u]) ans -= max(0, mx[u] - out[u]);//分讨情况:对于一个在连通块上不为根的关键点 u
for (int v : G[u]) if (v != fa && cnt_key[v]) ans -= max(0, (out[v] - 1) - (mx[v] + 1));//分讨情况:对于一个在连通块上为根的关键点 u
} else {
//分讨情况:对于一个在连通块上的非关键点
G[u].emplace_back(0), mx[0] = out[u] - 1, cnt_key[0] = cnt_key[1] - cnt_key[u];//将原树上的父亲视为儿子
sort(G[u].begin(), G[u].end(), [&](int x, int y) { return mx[x] > mx[y]; });
while (!G[u].empty() && !cnt_key[G[u].back()]) G[u].pop_back();
int vismx = 0;//是否已经有比当前儿子子树最大距离更大的儿子被划分到 u 的“子树”内,如果有的话“子树”内最大距离是多少
for (int v : G[u]) if (v != fa) {
if (vismx && cnt_key[v] && v != G[u].back()) ans -= max(0, vismx - (mx[v] + 1));
vismx = max(vismx, mx[v] + 1);
}
}
}
int main() {
IOS;
cin >> n;
for (int u, v, i = 1; i < n; ++i) cin >> u >> v, G[u].emplace_back(v), G[v].emplace_back(u);
cin >> S; S = '#' + S;
dfs(1, 0), Get_out(1, 0), Get_ans(1, 0);
cout << ans + 1 << '\n';
return 0;
}