cf1521 D. Nastia Plays with a Tree
题意:
给定一棵树。每次操作可以删一条边同时加一条边,问把树变成一条链至少要几次操作
思路:
官方题解看不懂,狠狠参考了几篇洛谷题解 https://www.luogu.com.cn/problem/solution/CF1521D
法一:贪心
如果节点 \(u\) 的后代是一条链,即 \(u\) 和 \(u\) 的所有后代都最多只有一个儿子,就把 \(u\) 称为直链点;如果 \(u\) 恰有两个儿子且两个儿子都是直链点,就把 \(u\) 称为凸链点
做 dfs 到 \(u\) 时,\(u\) 的儿子们都已经处理好,即每个儿子要么是直链点要么是凸链点。现在考虑要把 \(u\) 处理成啥点:如果让 \(u\) 变成凸链点所需的操作数等于变成直链点,就让 \(u\) 变成直链点;否则一定变成凸链点需要的次数一定比直链点少一次,就让 \(u\) 变成凸链点。
如果某儿子 \(v\) 是凸链点,那一定不能留着,要删边 \(u-v\)
具体讨论一下:设 \(cnt\) 为 \(u\) 的儿子数,
若只有一个儿子或者没儿子,不用操作;
若儿子全是凸链点,就花 \(cnt\) 次操作把 \(u\) 变成直链点。虽然花 \(cnt\) 次操作也能变成凸链点,但直链点比较灵活,变直链点的话后面操作空间比较大;
若有一个儿子是直链点,就花 \(cnt-1\) 次操作把其他儿子(凸链)都摘下来接到直链点后,使 \(u\) 变成直链点;
若有至少两个儿子是直链点,令这两个儿子不动,其他儿子接在他俩后面,即花费 \(cnt-2\) 次操作让 \(u\) 变成凸链点。
代码怎么写呢?注意可以先删所有要删的边,再加边。上面的删边操作实际上把树切成了若干连通块,每块都是一条链,最后加(块数-1)条边把这些链连成一条就行了。我觉得删边不好表示,就用不删的边建了个新图,最后在新图上dfs找出所有的链
//代码就dfs1函数值得看看,其他都是初始化、dfs2找连通块、输出答案等废话
const signed N = 5 + 2e5;
int n, d[N];
vector<int> G[N], _G[N];
vector<PII> del;
bool vis[N]; int idx, hh[N], tt[N]; //每条链的头尾
void init(int n) {
for(int i = 0; i <= n; i++)
G[i].clear(), _G[i].clear(), d[i] = 0,
hh[i] = tt[i] = vis[i] = 0;
del.clear();
idx = 0;
}
void dfs1(int u, int fa) {
for(int v : G[u]) if(v != fa)
dfs1(v, u);
int zhi = 0; //直链数量
for(int v : G[u]) if(v != fa) {
if(zhi < 2 && d[v] <= 2)
zhi++, _G[u].pb(v), _G[v].pb(u);
else del.pb({u,v}), d[u]--, d[v]--;
}
}
void dfs2(int u, int fa) {
vis[u] = 1;
if(d[u] == 1) {
if(!fa) hh[idx] = u;
else tt[idx] = u;
}
for(int v : _G[u]) if(v != fa)
dfs2(v, u);
}
void sol() {
cin >> n;
init(n);
for(int i = 1; i < n; i++) {
int x, y; cin >> x >> y;
G[x].pb(y), G[y].pb(x);
d[x]++, d[y]++;
}
dfs1(1, 0);
//在删边后的图中找连通块,即链
for(int u = 1; u <= n; u++)
if(d[u] == 0) hh[idx] = tt[idx] = u, idx++;
else if(d[u] == 1 && !vis[u]) dfs2(u, 0), idx++;
cout << del.size() << endl;
for(int i = 0; i < idx-1; i++)
cout << del[i].fi << ' ' << del[i].se << ' ',
cout << tt[i] << ' ' << hh[i+1] << endl;
}
法二:dp
仅考虑 \(u-\)子树,\(f_{u,1}\) 表示 \(u\) 是某条链的端点的答案,\(f_{u,0}\) 表示所有情况的答案
记 \(v_i\) 为 \(u\) 的儿子,有三种情况:
\(u\) 跟所有儿子断开,\(x_1=\sum f_{v_i,0}\)
\(u\) 跟一个儿子连接,\(x_2=max\{f_{v_i,1}+\sum\limits_{j\neq i} f_{j,0}\}\)
\(u\) 跟两个儿子连接(即 \(u\) 属于一条凸链),\(x_3=\max \{ f_{i,1}+f_{j,1}+\sum f_{k,0} \},i,j,k\) 两两不相等
为了输出方案要记录转移。代码懒得写。

浙公网安备 33010602011771号