题目大意: 给一棵由黑白点组成的树,如果一个黑点无法到达所有离他最远的点,他就会不开心,问有多少种方式使得删掉一个白点后不开心的黑点数最大。
题解
首先这题用到一个结论:树上任意一点到离他最远的点的路径一定经过树的中心(不是重心),那么对于这题,我们就可以先找到树的中心,将它定为树根,这样可以保证每条到最远点的路径都经过根。
我们都知道,找树的中心要先找树的直径,因为这题只有黑点要找最远的黑点,所以我们找直径时只看黑点,不看白点,具体代码是这样的:
//应该都知道找直径是要两次dfs找最远点的吧qwq,但还是有必要提一下
void dfs1(int x,int fa,int *dis)//dis数组记录每个点到出发点的距离
{
if(dis[x]>dis[furt])furt=x;//furt即furthest,记录离出发点最远的黑点
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y; if(y==fa)continue;
dis[y]=dis[x]+e[i].z; dfs1(y,x,dis);
}
}
主函数中就是这么一小段:
dfs1(1,0,a); a[furt]=0; dfs1(furt,0,a);
接下来枚举每一个白点,统计将它删除后有多少个不高兴的黑点。以及为了方便,这里做个定义:如果一个点 x x x 在根的儿子 y y y 的子树内,那么设 t o p [ x ] = y top[x]=y top[x]=y。
由于我们将中心定成了根,那么对于任意一个黑点 x x x,离他最远的黑点就是不在 t o p [ x ] top[x] top[x] 的子树内的离根最远的黑点,于是我们需要统计一下,根的每个儿子的子树内离根最远的黑点到根的距离以及数量。
另外,我们还需要统计出所有黑点中离根最远的是在哪个儿子的子树中,最远的记为 m a 1 ma1 ma1,次远的记作 m a 2 ma2 ma2,有可能有两个儿子都含有最远黑点,但如果有三个或以上的儿子含有最远黑点,就会发现,删掉任意一个白点后,只有这个白点内的黑点会不高兴,一定不会影响到其它黑点。
以及,删去一个白点 x x x 后,在 t o p [ x ] top[x] top[x] 的子树内的且不在 x x x 子树内的黑点一定不受影响。
接下来就是分类讨论了,分类讨论这部分配合代码食用更佳:
for(int i=1;i<=n;i++)//枚举白点
{
if(col[i]||i==root)continue;
int ans_=black[i];
//black[i]表示i的子树内黑点的个数,显然,将i删去后它子树内的黑点一定都会不高兴
//下面就是统计子树外的黑点有多少个会不高兴,不高兴的前提是那个黑点的所有最远点都在i的子树内
if(black[i]&&a[i]==a[top[i]]&&b[i]==b[top[i]])
//对black[i]的判断大家都懂,后面判断的是在top[i]的子树内离根最远的黑点是否都在i的子树内
//如果不是,那么删去i后对其它的黑点肯定都没有影响,这个想想就能明白
{
if(top[i]==ma1)//假如top[i]的子树内有最远黑点
{
if(a[ma1]==a[ma2])ans_+=black[ma2];
//假如最远和次远一样远,那么删去i后只有次远的整棵子树会到不了离他们最远的黑点
else ans_+=m-black[ma1];//假如最远比次远要远,那么除了最远的这棵子树,其他点都会受影响
}
else if(top[i]==ma2)ans_+=black[ma1];//如果是次远,那么只有最远会受影响
}
if(ans_>ans)ans=ans_,anss=1;//更新答案
else if(ans_==ans)anss++;
}
完整代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010
int n,m,col[maxn];
struct edge{int y,z,next;};
edge e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y,int z)
{
e[++len]=(edge){y,z,first[x]};
first[x]=len;
}
int a[maxn],b[maxn],furt=1;
void dfs1(int x,int fa,int *dis)//找离出发点最远的点
{
if(dis[x]>dis[furt])furt=x;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y; if(y==fa)continue;
dis[y]=dis[x]+e[i].z; dfs1(y,x,dis);
}
}
int abs(int x){return x<0?-x:x;}
int black[maxn],top[maxn],root=0;
void dfs2(int x,int fa,int dis)//统计每个点子树内的黑点数以及最远的黑点
{
if(fa==root)top[x]=x; else top[x]=top[fa];
black[x]=col[x]; if(col[x])a[x]=dis,b[x]=1;
for(int i=first[x];i;i=e[i].next)
{
int y=e[i].y; if(y==fa)continue;
dfs2(y,x,dis+e[i].z);
if(a[y]>a[x])a[x]=a[y],b[x]=b[y];
else if(a[y]==a[x])b[x]+=b[y];
black[x]+=black[y];
}
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1,x;i<=m;i++)
scanf("%d",&x),col[x]=1;
for(int i=1,x,y,z;i<n;i++)
scanf("%d %d %d",&x,&y,&z),buildroad(x,y,z),buildroad(y,x,z);
dfs1(1,0,a);
a[furt]=0; int p=furt;
dfs1(furt,0,a); dfs1(furt,0,b);
for(int i=1;i<=n;i++)
{
if(a[i]+b[i]==b[p])//假如该点在直径上
{
if(root==0)root=i;
//并且这个点更靠近中点,就更新树的中心
else if(abs(a[i]-b[i])<abs(a[root]-b[root]))root=i;
}
}
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
dfs2(root,0,0);
int ma1=0,ma2=0;
for(int i=first[root];i;i=e[i].next)
{
int y=e[i].y; if(!black[y])continue;
if(a[y]>a[ma1])ma2=ma1,ma1=y;//找最远和次远
else if(a[y]>a[ma2])ma2=y;
}
for(int i=first[root];i;i=e[i].next)//假如有三个一样的最远,那么一定不会影响子树外的点
if(e[i].y!=ma1&&e[i].y!=ma2&&a[e[i].y]==a[ma1]){ma1=ma2=0;break;}
int ans=0,anss=0;
if(!col[root])ans=m,anss=1;//统计根的答案
for(int i=1;i<=n;i++)
{
if(col[i]||i==root)continue;
int ans_=black[i];
if(black[i]&&a[i]==a[top[i]]&&b[i]==b[top[i]])
{
if(top[i]==ma1)
{
if(a[ma1]==a[ma2])ans_+=black[ma2];
else ans_+=m-black[ma1];
}
else if(top[i]==ma2)ans_+=black[ma1];
}
if(ans_>ans)ans=ans_,anss=1;
else if(ans_==ans)anss++;
}
printf("%d %d",ans,anss);
}