CF1338D Nested Rubber Bands
玩了一下样例,以为是最大独立集板子,交了一发第 \(4\) 个点就错了(笑)
仔细钻研了一下错的数据,发现一个点如果在 \(3\) 个子树内都有选点就会出错。
再钻研一下发现不对,菊花图显然不满足这个判断。为什么菊花图有问题呢?发现原来菊花图属于当前点 \(u\) 不选但所有 \(v\) 能选上的情况。那再试着推广一下这个情况。
于是接着发现一个性质:在一棵一般的树中,对于一个 \(u\),如果 \(u\) 不选,并且选了 \(3\) 个以上的直接连点比如 \(x, y, z\),那么 \(x, y, z\) 所在的子树中就 不可能再选别的点了(画一画图就知道一定会和 \(u\) 交叉)
所以进一步总结:问题转化为选一条链和所有与连直接相连的点,求这个子集的最大独立集。
所以本题是一道最大独立集的变式,肯定还是考虑树形 DP。
由上述分析可知,状态定义的关键就在于当前点 \(u\) 在不在序列里面,这和最大独立集类似。
对于一个点 \(i\),我们用 \(f_{i, 0}\) 表示在以 \(i\) 为根的子树中,\(i\) 作为链的一端,并且 \(i\) 不在序列中,序列的最大长度。\(f_{i, 1}\) 就是 \(i\) 在序列中的情况。
转移:
\[
f_{v, 1} + d_u - 2 \to f_{u, 0} \\
f_{v, 0} + d_u - 2 \to f_{u, 0} \\
f_{v, 0} + 1 \to f_{u, 1} \\
\]
\(d_u - 2\) 可以理解成针对局部是菊花图的情况。(或者说其实就是考虑和链直接相连的点的贡献)
边转移边更新:(人话就是把两条链拼起来,注意此时 \(f_{u}\) 都是用 \(f_{v}\) 转移之前的状态)
\[
f_{u, 0} + f_{v, 0} \to ans \\
f_{u, 0} + f_{v, 1} \to ans \\
f_{u, 1} + f_{v, 0} \to ans \\
\]
最后再更新:
\[
f_{u, 1} \to ans
\]
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l); i <= (r); ++ i)
#define G(i,r,l) for(int i(r); i >= (l); -- i)
using namespace std;
using ll = long long;
const int N = 1e5 * 2;
vector<int> G[N];
int f[N][5];
int n, ans = 0;
void dfs(int u, int fa){
int d = G[u].size() - 2;
f[u][1] = 1;
f[u][0] = d;
for(auto v : G[u]){
if(v == fa) continue;
dfs(v, u);
ans = max(ans, f[u][0] + f[v][0]);
ans = max(ans, f[u][0] + f[v][1]);
ans = max(ans, f[u][1] + f[v][0]);
f[u][0] = max(f[u][0], max(f[v][1] + d, f[v][0] + d));
f[u][1] = max(f[u][1], f[v][0] + 1);
}
ans = max(ans, f[u][1]);
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
cin >> n;
F(i, 1, n - 1){
int u, v;
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, 0);
cout << ans << '\n';
return fflush(0), 0;
}

浙公网安备 33010602011771号