「AT2210 木の問題 / Problem on Tree」
题目大意
在树上按顺序选出一些点,使得相邻的两点的树上路径中不存在其他被选的点,求可以选点的最大个数.
分析
这里给出一种比较麻烦的非常暴力的做法.
考虑直接树形 \(\operatorname{dp}\).首先可以发现对于一棵树这棵树的所有度为 \(1\) 的点必然是可以同时选择的(这里度指无根树中一个点所连接的点的个数).有根树比较难处理,下面假定 \(1\) 为根.那么如果需要选择一棵子树中的点且选择的这些点并不是在整个选择点的开头位置或者结尾位置,这颗子树中第一个选择的点前面存在点,最后一个选择的点后面也有点,那么这颗子树必然是选择叶节点最优(证明略,可以自行画图理解)现在设 \(f_i\) 表示当前这颗子树不打算再上来的最多可选点数(对于开头位置应为不打算下去,但是这两种情况本质相同),为了表示方便设 \(g_i\) 表示以 \(i\) 为根的子树中度为 \(1\) 的点的个数(根节点的度也可能为 \(1\)).
可以得到转移方程:
(其中 \(j\) 为 \(i\) 的子节点,\(f_j+1\) 表示选择一个节点往下并且不上来,那么显然 \(i\) 这个点也可以选择,\(f_j+g_i-g_j\) 表示选择一个点向下,并且走完 \(i\) 的其他子树的叶节点)
那么答案也是分成两种情况,一种是起点为根节点,也就是 \(f_i\),另一种情况比较麻烦,没法确定起点和终点的位置,所以考虑在它们 \(\operatorname{LCA}\) 的位置计算答案,这里还需要分成两种情况,一种是选择一个起点和一种终点,并且选择了当前这个点(\(i\)),那么就是
(其中 \(j,k\) 为 \(i\) 的两个不同的子节点)
这种情况可以选择每个节点的子节点(\(j\))中 \(f_j\) 的最大值和次大值得出.
另一种情况为不选择这个但是选择这个点所连出去的度为 \(1\) 的节点,也就是:
这种情况可以选择每个几点的子节点(\(j\))中 \(f_j-g_j\) 的最大值和次大值得出.
这样这题就做完了.
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;last<=i;--i)
namespace IO
//快读模板
using namespace IO;
using namespace std;
const int MAXN=1e6+5;
template<int MAXN,int MAXM>class Edge
{
//存图模板
#define FOR(now_edge,now) //遍历 now 的出边
#define TO(now_edge) //出边
};
Edge<MAXN,MAXN<<1>e;
int n;
int f[MAXN],g[MAXN];
bool le[MAXN];//判断一个节点是否为叶节点
int answer=0;
void DFS(int now=1,int father=0)//计算 f,g
{
bool flag=1;
FOR(e,now)
{
if(TO(e)^father)
{
flag=0;
DFS(TO(e),now);
g[now]+=g[TO(e)];
}
}
le[now]+=flag;
g[now]+=flag;
f[now]=g[now];
FOR(e,now)
{
if(TO(e)^father)
{
f[now]=Max(f[now],f[TO(e)]+g[now]-g[TO(e)],f[TO(e)]+1);//转移
}
}
}
void DFS_2(int now=1,int father=0)//计算每个节点为 LCA 时的答案
{
int max1=0,max2=0;//f[j] 的最大值和次大值
int max3=0,max4=0;//f[j]-g[j] 的最大值和次大值
FOR(e,now)
{
if(TO(e)^father)
{
DFS_2(TO(e),now);
if(max1<f[TO(e)])
{
max2=max1;
max1=f[TO(e)];
}
else
{
if(max2<f[TO(e)])
{
max2=f[TO(e)];
}
}
if(max3<f[TO(e)]-g[TO(e)])
{
max4=max3;
max3=f[TO(e)]-g[TO(e)];
}
else
{
if(max4<f[TO(e)]-g[TO(e)])
{
max4=f[TO(e)]-g[TO(e)];
}
}
}
}
answer=Max(answer,max1+max2+1,g[1]+max3+max4);//统计两种情况
}
int main()
{
Read(n);
int u,v,tot=0;
REP(i,1,n-1)
{
Read(u,v);
tot+=u==1||v==1;
e[u]+=e[v]+=u;
}
if(tot==1)//特判根节点为叶节点的情况
{
le[1]=1;
g[1]=1;
}
DFS();
DFS_2();
Writeln(Max(answer,f[1]));//输出答案
return 0;
}