uoj #139. 【UER #4】被删除的黑白树 dfs序 贪心
139. 【UER #4】被删除的黑白树
题目连接
Description
很久很久以前,有一棵树加入了 UOJ 群。
这天,在它讨论“一棵树应该怎么旋转”的时候一不小心被删除了,变成了被删除的树。
突然间,它突然发现它失去了颜色,变成了一棵纯白的树。这让它感觉很焦躁,于是它来拜托你给自己染上一些颜色。
我们可以把它描述为一棵 n 个节点的有根树(默认树的根为 1 号节点),所有非根的度数为 1 的节点被称为叶子节点。最开始所有的节点都是白色的。
现在你需要选出一些节点并把这些节点染成黑色的。为了迎合树的审美,你的染色方案必须要满足所有叶子节点到根路径上的黑色节点个数相同。
你发现黑色节点个数越多,树就会越高兴,所以你想要知道在所有合法的染色方案中,黑色节点总个数最多是多少。
Input
第一行一个正整数 n 表示树的节点个数。
接下来的 n−1 行,每行是两个整数 u,v (1≤u,v≤n,u≠v) 表示树上的一条边。
Output
一个整数,表示在所有合法方案中黑色节点的最多数量。
Sample Input
7
1 2
1 3
2 4
2 5
3 6
3 7
Sample Output
7
HINT
题意
题解:
首先不难想到肯定贪心的从最底下开始染色,所有叶子节点从底下一层一层往染色。但是显然行不通,因为到了两个点的交界处就会出现问题。
然后我有调整了一下,从深度最浅的点开始染色。第一个点就直接染到根节点(不难证明每个叶子节点所在链染色最多,一定是整个树染色最多,而链染色最多不会超过最浅的点的深度),然后再找第二浅的点开始往上染色。
这样做是没有问题的,但是做法比较麻烦,每次需要知道上方有点被染色了,还有各种细节,于是我放弃了。。。
其实我们可以预处理每个点x离它最近的叶子节点的深度mx[x],这一步是关键,之后的自己想想应该能想出来。
然后dfs,假设我已经知道了这个点到根节点有tt个点染成了黑色,deep表示最浅的叶节点的深度,dp[x]表示节点x的深度,那么如果mx[x]-dp[x]+tt>=deep,x点就不染色,一开始tt=0。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100050
int n,ans,deep=N,dp[N],mi[N];
int last[N],tot;
struct Edge{int from,to,s;}edges[N<<1];
void AddEdge(int x,int y)
{
edges[++tot]=Edge{x,y,last[x]};
last[x]=tot;
}
template<typename T>void read(T&x)
{
ll k=0; char c=getchar();
x=0;
while(!isdigit(c)&&c!=EOF)k^=c=='-',c=getchar();
if (c==EOF)exit(0);
while(isdigit(c))x=x*10+c-'0',c=getchar();
x=k?-x:x;
}
void read_char(char &c)
{while(!isalpha(c=getchar())&&c!=EOF);}
void dfs(int x,int pre)
{
dp[x]=dp[pre]+1;
for(int i=last[x];i;i=edges[i].s)
{
Edge &e=edges[i];
if (e.to==pre)continue;
dfs(e.to,x);
if (mi[x]==0||mi[e.to]<mi[x])mi[x]=mi[e.to];
}
if (mi[x]==0)
{
mi[x]=dp[x];
deep=min(deep,dp[x]);
}
}
void solve(int x,int pre,int tt)
{
if (mi[x]-dp[x]+tt<deep) ans++,tt++;
for(int i=last[x];i;i=edges[i].s)
{
Edge &e=edges[i];
if (e.to==pre)continue;
solve(e.to,x,tt);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("aa.in","r",stdin);
#endif
read(n);
for(int i=1;i<=n-1;i++)
{
int x,y;
read(x); read(y);
AddEdge(x,y);
AddEdge(y,x);
}
dfs(1,0);
solve(1,0,0);
printf("%d\n",ans);
}