Foreword\text{Foreword}Foreword
《小清新》树形 dp。
其实本题没有那么(重读)恶心,但我一开始写完 x=0x=0x=0 后眼瞎没有看到 x=1,2x=1,2x=1,2 时也必然是最优方案(这种东西不黑体吗…)可以直接无视,还在苦苦的写强制选链的特殊情况,被狠狠的恶心到了。
这个故事告诉我们,要仔细审题!
感觉自己的做法还是挺舒服的,虽然核心转移看的有些长(也只有 40 行左右),但没有太多的分类讨论,和有些代码枚举儿子然后直接取十几行 max\maxmax 相比感觉更加阳间。
Solution\text{Solution}Solution
题意算比较简洁清晰了,再具体一些大概就是选出两条可重点不可重边的链,将树分割成尽可能多的连通块。
(一开始我受 希望 那个题的影响,尝试了好一会用度数和点数简洁的表示出连通块数目来简化 dp,其实是在玩泥巴,直接做就很方便。)
Definition\text{Definition}Definition
设置状态 fx,i,df_{x,i,d}fx,i,d 表示 xxx 节点子树内选择了 iii 条已经确定的链,子树内 的联通块最大数目。
ddd 的定义有些复杂:
在 xxx 作为父亲的时候,d∈[0,4]d\in [0,4]d∈[0,4] 表示的是 xxx 被删除,与 xxx 相连的儿子的个数,d=5d=5d=5 表示 xxx 不被删除。
在 xxx 作为儿子的时候,d∈[0,1]d\in [0,1]d∈[0,1] 表示 xxx 被删除,且与父亲相连/不相连。d=5d=5d=5 表示 xxx 不被删除。
Transfer\text{Transfer}Transfer
Part 1
d=5d=5d=5,xxx 不删时:xxx 必然不会和儿子相连,儿子要么删了但不和父亲连(d=0d=0d=0),要么根本不删 (d=5d=5d=5),不删的时候由于父子都不删,两个联通块合并成一个,所以答案要减一。
fx,i,5+fson,j,5−1→fx,i+j,5f_{x,i,5}+f_{son,j,5}-1\to f_{x,i+j,5}fx,i,5+fson,j,5−1→fx,i+j,5
fx,i,5+fson,j,0→fx,i+j,5f_{x,i,5}+f_{son,j,0}\to f_{x,i+j,5}fx,i,5+fson,j,0→fx,i+j,5
d∈[0,4]d\in[0,4]d∈[0,4] 时:xxx 可以和儿子相连也可以不连,不连的时候儿子是否删除都可以,对应转移分别就是:
fx,i,d+fson,j,1→fx,i+j,d+1f_{x,i,d}+f_{son,j,1}\to f_{x,i+j,d+1}fx,i,d+fson,j,1→fx,i+j,d+1
fx,i,d+fson,j,0→fx,i+j,df_{x,i,d}+f_{son,j,0}\to f_{x,i+j,d}fx,i,d+fson,j,0→fx,i+j,d
fx,i,d+fson,j,5→fx,i+j,df_{x,i,d}+f_{son,j,5}\to f_{x,i+j,d}fx,i,d+fson,j,5→fx,i+j,d
这样 xxx 从所有儿子得到的转移就全完事了,接下来我们需要把 fx,i,df_{x,i,d}fx,i,d 的 ddd 的定义从“父亲形态”转化成“儿子形态”,以使它可以继续向父亲转移。
Part 2
d=5d=5d=5 时:xxx 没选就是没选,没什么好说的。
fx,i,5→fx,i,5f_{x,i,5}\to f_{x,i,5}fx,i,5→fx,i,5
d=0d=0d=0 时:注意我们之前是强制让 xxx 被删除的,所以我们不能直接让它转移到 fx,i,0f_{x,i,0}fx,i,0。为了删除它,要么使其单独一个点成链,要么让它成为链尾和父亲相连,对应就是:
fx,i,0→fx,i+1,0f_{x,i,0}\to f_{x,i+1,0}fx,i,0→fx,i+1,0
fx,i,0→fx,i,1f_{x,i,0}\to f_{x,i,1}fx,i,0→fx,i,1
d=2/4d=2/4d=2/4 时:向 xxx 连接的偶数条边必然两两成链:
fx,i,d→fx,i+d/2,0f_{x,i,d}\to f_{x,i+d/2,0}fx,i,d→fx,i+d/2,0
d=1/3d=1/3d=1/3 时:单出来的一条可以把 xxx 当成链头停止,也可以继续向父亲延伸:
fx,i,d→fx,i+d/2+1,0f_{x,i,d}\to f_{x,i+d/2+1,0}fx,i,d→fx,i+d/2+1,0
fx,i,d→fx,i+d/2,1f_{x,i,d}\to f_{x,i+d/2,1}fx,i,d→fx,i+d/2,1
Part 3
这样看起来就做完了?
但是你一测发现过不去样例!
仔细一看,是被两个点一条边的情况 hackhackhack 了。
一般的,当一个图长成深度为 1 的菊花的时候,我们都会出问题。
我们缺少了在一个点连续摆烂的情况。
这个东西有两种解决方案:
第一种是当 xxx 被删除的时候,可以原地多选几条链,即:
fx,i,0→fx,i+1,0f_{x,i,0}\to f_{x,i+1,0}fx,i,0→fx,i+1,0
fx,i,1→fx,i+1,1f_{x,i,1}\to f_{x,i+1,1}fx,i,1→fx,i+1,1
第二种是求出度数最大点的度数,让答案和这个度数取个 max\maxmax。
第二种虽然看起来更加简单,跑起来也稍微快一点,但如果在考场上,本人还是建议第一种,毕竟多说多错,直接给一个合法转移,让 std::max
做决策而不是自己归纳贪心,肯定更加稳妥。
如果是做练习当然就精益求精啦。
Code\text{Code}Code
代码就简单了,把上面的转移敲出来即可。
(滚动数组无脑好用,不会互相转移出 bug,强推)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define ok debug("OK\n")
using namespace std;const int N=1e5+100;
const int M=2e5+100;
const int inf=1e9;inline ll read(){ll x(0),f(1);char c=getchar();while(!isdigit(c)) {if(c=='-')f=-1;c=getchar();}while(isdigit(c)) {x=(x<<1)+(x<<3)+c-'0';c=getchar();}return x*f;
}int n;
int f[N][3][6];
int tmp[2][3][6],now,pre;
vector<int>v[N];
inline void Max(int &x,int y){y>x?x=y:0;return;
}
void dfs(int x,int fa){for(int to:v[x]){if(to==fa||vis[to]) continue;dfs(to,x);}now=1;pre=0;memset(tmp,-0x3f,sizeof(tmp));tmp[now][0][5]=1;tmp[now][0][0]=0;for(int s:v[x]){if(s==fa||vis[s]) continue;swap(now,pre);memset(tmp[now],-0x3f,sizeof(tmp[now]));for(int i=0;i<=2;i++){for(int j=0;i+j<=2;j++){Max(tmp[now][i+j][5],tmp[pre][i][5]+f[s][j][5]-1);Max(tmp[now][i+j][5],tmp[pre][i][5]+f[s][j][0]);for(int d=0;d<=4;d++){Max(tmp[now][i+j][d],tmp[pre][i][d]+f[s][j][0]);Max(tmp[now][i+j][d],tmp[pre][i][d]+f[s][j][5]);if(d+1<=4)Max(tmp[now][i+j][d+1],tmp[pre][i][d]+f[s][j][1]);}} }}memset(f[x],-0x3f,sizeof(f[x]));for(int i=0;i<=2;i++){Max(f[x][i][5],tmp[now][i][5]);if(i+1<=2) Max(f[x][i+1][0],tmp[now][i][0]);Max(f[x][i][1],tmp[now][i][0]);for(int j=1;j<=4;j++){if(j&1){if(i+j/2<=2) Max(f[x][i+j/2][1],tmp[now][i][j]);if(i+(j+1)/2<=2) Max(f[x][i+(j+1)/2][0],tmp[now][i][j]);}else if(i+j/2<=2) Max(f[x][i+j/2][0],tmp[now][i][j]);}}for(int i=1;i<=2;i++){Max(f[x][i][0],f[x][i-1][0]);Max(f[x][i][1],f[x][i-1][1]);}return;
}
void work0(){for(int i=1;i<n;i++){int x=read(),y=read();v[x].push_back(y);v[y].push_back(x);}dfs(1,0);printf("%d\n",max(f[1][2][0],f[1][2][5]));return;
}
signed main(){
#ifndef ONLINE_JUDGEfreopen("a.in","r",stdin);freopen("a.out","w",stdout);
#endifint T=read(),op=read();while(T--){n=read();for(int i=1;i<=op;i++) read(),read();work0();for(int i=1;i<=n;i++) v[i].clear();}return 0;
}
/*
*/