题意
有一棵树,树上的每一个点可以看做一个小镇,有的小镇有集市,有的没有,每一个小镇都被距离它最近且编号(树上的编号)最小的集市所服务。现在要再建一个集市,问这个集市最多可以服务多少个小镇。
Part 0
初始状态中,每一个居住区都会被一个集市服务,而每个居住区被哪个集市服务我们是可以通过两遍DFS所预处理出来的,也就是利用下面这个浅显的性质。
如果这个小镇有集市,那么离它最近的集市就是它自己。
如果这个小镇没有集市,那么离它最近的集市在它的儿子子树内。
利用这个性质就可以树形DP,记录每个小镇被哪个集市服务,在转移。第二遍DFS把它上面那颗子树的信息也转移过来即可。
Part1
由于有集市的小镇一定不会被新建的集市服务,那么新建的集市服务到的小镇一定是没有集市的。那么我们只用在枚举重心时对于一条路径A–B,如果这条路径的长度小于当前服务A的集市到A的距离,或是这条路径的长度与当前服务A的集市到A的距离相等,但是B的编号比服务A的这个集市的编号小,都会使在B点建集市时使A被B服务。对于在A点建集市B点是否被服务的判断也是同理的。
(以下中的其他小镇指当前枚举的重心管辖的其他小镇)也就是对于一个没有集市的小镇X,它到重心的距离为D,在这里建一个集市会服务到其他没有集市小镇必须满足以下条件。服务它的集市(其编号在下面记为Y)到它的距离减去他到重心的距离(下面称为W) > D >D >D,或是两者相等,但是 Y < X Y<X Y<X。也就是求 ( W , Y ) > ( D , X ) (W,Y)>(D,X) (W,Y)>(D,X)的小镇的个数。也就是 ( 的 个 数 ) n − ( ( W , Y ) ≤ ( D , X ) 的 个 数 ) (的个数)n-((W,Y)\leq(D,X)的个数) (的个数)n−((W,Y)≤(D,X)的个数)。
怎么实现上述操作呢?我们可以用两个pair数组,(命名为A)一个存重心管辖的所有没有集市的小镇的当前服务该小镇的集市到该小镇的距离与这个集市的编号。(命名为B)一个存所有的没有集市的小镇到重心的距离以及这个小镇的编号。处理完一个重心的信息之后对A数组排个序,对于B数组的每一个元素在A元素中求一个lower_bound,再更新哪个小镇的答案即可。
对于不合法路径的去除用容斥。
#include<cstdio>
#include<algorithm>
#define M 100005
using namespace std;
void check_max(int &x,int y){if(x<y)x=y;}
void check_min(int &x,int y){if(x>y)x=y;}
int val[M];
int n;
struct E{
int to,nx,d;
}edge[M<<1];
int tot,head[M];
void Addedge(int a,int b,int d){//正向表存边
edge[++tot].to=b;
edge[tot].nx=head[a];
edge[tot].d=d;
head[a]=tot;
}
struct node{
int Belong,dis;
void Init(){Belong=dis=0;}
bool operator ==(const node &x)const{
return Belong==x.Belong&&dis==x.dis;
}
bool operator <(const node &x)const{
if(Belong==0)return 0;
if(x.Belong==0)return 1;
if(dis==x.dis)return Belong<x.Belong;
return dis<x.dis;
}
bool Change(node x){
if(x.Belong==0)return false;
if(x.dis<dis||Belong==0){
Belong=x.Belong,dis=x.dis;
return true;
}else if(x.dis==dis&&Belong>x.Belong){Belong=x.Belong;return true;}
return false;
}
void Print(int now){
printf("now:%d Belong:%d dis:%d\n",now,Belong,dis);
}
}Mn[M],tmp[M],dp[M];
//Mn 距离每个点最近的集市的编号与到这个点的距离 tmp同题解中的B数组,dp同A数组
int sz_dp;
void dfs(int now,int fa){//树形DP将在这个点下面的子树的信息更新并收集
if(val[now])Mn[now]=(node){now,0};
else Mn[now].Init();
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(nxt==fa)continue;
dfs(nxt,now);
node nxtd=Mn[nxt];
nxtd.dis+=edge[i].d;
Mn[now].Change(nxtd);
}
}
void redfs(int now,int fa){//由这个点上面的子树的信息更新这个点
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(nxt==fa)continue;
node nxtd=Mn[now];
nxtd.dis+=edge[i].d;
Mn[nxt].Change(nxtd);
redfs(nxt,now);
}
}
int sz_S;
int sz[M],mx_sz[M],tot_sz,Root;
bool mark[M];
void Get_Root(int now,int fa){
sz[now]=1,mx_sz[now]=0;
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt]||nxt==fa)continue;
Get_Root(nxt,now);
sz[now]+=sz[nxt];
check_max(mx_sz[now],sz[nxt]);
}
check_max(mx_sz[now],tot_sz-sz[now]);
if(mx_sz[now]<mx_sz[Root]||!Root)Root=now;
}
void Get_dis(int now,int fa,int dis){
if(!val[now]){//如果这个小镇没有集市
tmp[++sz_dp]=(node){now,dis};//存下当前这个小镇到重心的距离与这个小镇的编号
dp[sz_dp]=(node){Mn[now].Belong,Mn[now].dis-dis};
//存下这个小镇到离它最近的集市的距离与可以更新它的那个新建的集市到重心的最大允许距离
}
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt]||nxt==fa)continue;
Get_dis(nxt,now,dis+edge[i].d);
}
}
int ans[M];
void Get_ans(int now,int fa,int dis,int op){
sz_dp=0;
Get_dis(now,fa,dis);
sort(dp+1,dp+sz_dp+1);
for(int i=1;i<=sz_dp;i++){
int x=lower_bound(dp+1,dp+sz_dp+1,tmp[i])-dp;//求(W,Y)<=(D,X)的个数(字母的含义在题解中)
ans[tmp[i].Belong]+=(sz_dp-x+1)*op;
}
}
void Devide(int now){//点分治
mark[now]=1;
Get_ans(now,0,0,1);
for(int i=head[now];i;i=edge[i].nx){
int nxt=edge[i].to;
if(mark[nxt])continue;
Get_ans(nxt,now,edge[i].d,-1);//容斥
tot_sz=sz[nxt],Root=0;
Get_Root(nxt,now);
Devide(Root);
}
}
void Init(){
tot=0;
for(int i=1;i<=n;i++)mark[i]=0,head[i]=0,ans[i]=0;
}
int main(){
while(~scanf("%d",&n)){
Init();
for(int i=1;i<n;i++){
int a,b,d;
scanf("%d%d%d",&a,&b,&d);
Addedge(a,b,d);
Addedge(b,a,d);
}
for(int i=1;i<=n;i++)scanf("%d",&val[i]);
dfs(1,0);
redfs(1,0);
tot_sz=n,Root=0;
Get_Root(1,0);
Devide(Root);
int mx=0;
for(int i=1;i<=n;i++)check_max(mx,ans[i]);//找出最优的答案
printf("%d\n",mx);
}
return 0;
}