zoukankan      html  css  js  c++  java
  • Codeforces-348E Pilgrims

    #4342. CF348 Pilgrims

    此题同UOJ#11 ydc的大树

    Online Judge:Bzoj-4342,Codeforces-348E,Luogu-CF348E,Uoj-#11

    Label:树的直径,树形dp,神题

    题目描述

    在很久以前有一片土地被称为 Dudeland。Dudeland 包含 n 个城镇,它们用 n − 1 条双向道路连接起来。这些城镇通过道路可以两两互达。这里有 m 个修道院坐落在 m 个不同的城镇。每个修道院有一个教徒。在一年之始,每个教徒会选择离他最远的一个修道院。如果有多个,他会把所有的都列入清单。在 “BigLebowskiday” 里,每个教徒会随机选择一个清单里的城镇开始走去。Walter 讨厌教徒。他想尽可能的通过阻止他们的行程来让尽可能多的人不开心。他计划摧毁一个没有修道院的城镇。一个教徒如果在他的清单里没有任何一个城镇能去,他就会不开心。你需要求出 Walter 最多能让几个教徒不开心。除此之外,你还要计算他有多少种方法

    输入格式

    第一行包含两个整数 n,m,满足 (3 ≤ n ≤ 10^5), (2 ≤ m<n)

    接下来一行,有 m 个互不相同的整数,他们代表了有修道院的城镇的编号。

    接下来 n − 1 行,每行三个整数 (a_i,b_i,c_i),表示 (a_i,b_i) 之间有一条边权为 (c_i) 的边。((1 ≤ a_i,b_i ≤ n,c_i ≤ 1000))

    输出格式

    输出两个数:最多能让几个教徒不开心,以及有多少种方式达到这种效果。

    样例

    输入

    8 5
    7 2 5 4 8
    1 2 1
    2 3 2
    1 4 1
    4 5 2
    1 6 1
    6 7 8
    6 8 10
    

    输出

    5 1
    

    题解:

    这道题有两种解法:

    • 一种是复杂度为(O(NlogN))的树形dp,详见CF上的标程
    • 下面一种(O(N))做法,参考了这篇blog,uoj上出题人的O(N)思路,网上O(N)解法的思路都大致差不多,都根据树的直径来分类讨论但都没我写的详细

    O(N)解法:

    这道题要找树上离某点距离最远的点,联想到树的直径以及包含的性质。

    我们知道树的直径可能不止一条,但他们都交于一点——直径的中点。

    我们发现有时候直径上的中点可能会有两个——就是有两个点到两端的距离都相等,那么随便选哪一个其实都没影响,为了区分,我们将选择的那个中点重新命名为类中点,并且存在如下性质:

    对于树上的任意一点x,x在树上的最远路径必定经过上面所说的类中点。

    查询类中点代码如下,其实那三个搜索可以并为一个,但为了区分意义所以打三个

    bool is[N];//是不是修道院
    int len[N][2],P1=0,P2=0,diameter=0,root=0;
    void findpoint1(int x,int fa,int nowd){//找到直径的一个端点P1 
    	if(is[x]&&diameter<nowd)P1=x,diameter=nowd;
    	for(int i=0;i<e[x].size();i++){
    		int y=e[x][i].to;if(y==fa)continue;
    		findpoint1(y,x,nowd+e[x][i].d);
    	}
    }
    void findpoint2(int x,int fa){//找到直径的另一个端点P2,顺便预处理每个点离P1的距离 
    	if(is[x]&&diameter<len[x][0])P2=x,diameter=len[x][0];
    	for(int i=0;i<e[x].size();i++){
    		int y=e[x][i].to;if(y==fa)continue;
    		len[y][0]=len[x][0]+e[x][i].d;
    		findpoint2(y,x);
    	}	
    }
    void makedis(int x,int fa,int nowd){//预处理每个点离P2的距离 
    	len[x][1]=nowd;
    	for(int i=0;i<e[x].size();i++){
    		int y=e[x][i].to;if(y==fa)continue;
    		makedis(y,x,nowd+e[x][i].d);		
    	}
    }
    void choose_Root(){//找直径上的中点做root,那么每个点的最远路径必经过root 
    	findpoint1(1,0,0);
    	diameter=0;
    	findpoint2(P1,0);
    	makedis(P2,0,0);
    	int midata=1e9;
    	For(i,1,n){//在直径上选中点 
    		if(len[i][0]+len[i][1]!=len[P1][1])continue;
    		int d1=abs(len[i][0]-len[i][1]);
    		if(d1<midata){midata=d1;root=i;}
    	}
    }
    

    所以有什么用呢?我们可以把这个类中点提到树根位置,然后重新建树。这样子每个教徒要去树上距离它最远的修道院必须得经过树根。然后我们在dfs重新建树的时候预处理出一下东西:

    • (cnt[x]):以x为根的子树中含修道院的个数
    • (link[x]):以x为根的子树中的最大深度——也就是离树根的最长链长
    • (nums[x]):以x为根的子树中深度等于(link[x])的节点个数
    • (id[x]):我们断掉root后不是会形成一片森林吗,对森林中的每棵树进行编号,(id[x])就表示它属于哪棵树

    至于有什么用,,先继续往下看

    int cnt[N];//cnt[x]以x为根的子树中含修道院个数 
    int link[N],nums[N];//link[x]:x以及其子孙中离根的最长距离,nums有几个 
    int id[N]; 
    void dfs(int x,int fa,int dis){
    	if(fa==root)id[x]=x;
    	else id[x]=id[fa];
    	cnt[x]=is[x]?1:0;
    	for(int i=0;i<e[x].size();i++){
    		int y=e[x][i].to;if(y==fa)continue;
    		dfs(y,x,dis+e[x][i].d);
    		cnt[x]+=cnt[y];
    	}
    	if(cnt[x]){
    		link[x]=dis,nums[x]=1;
    		for(int i=0;i<e[x].size();i++){
    			int y=e[x][i].to;if(y==fa)continue;
    			if(link[y]>link[x])link[x]=link[y],nums[x]=nums[y];
    			else if(link[y]==link[x])nums[x]+=nums[y];
    		}
    	}
    }
    

    那么我们可以开始yy分类讨论了。

    (这一段的内容在上面关于(id[x])的定义中就提到过了)假设断掉root,这样就会形成森林(至少有两棵树,因为我们把类中点提做根),那么一棵树中的节点x想去到森林中距离它最远的点y,x必须从x所在的树到y所在的树。我们发现对答案真正有用的是含最长链的树,以及可能会用到含次长链的树——下面为了方便描述用A代替最长链,B代替次长链。

    于是下面代码中在part1部分先去找A的长度ma1,B的长度ma2——(直接一起更新树的id也可以,但细节比较多)。

    part2中,根据刚刚找到的ma1,ma2去找对应的树的编号id1,id2。其中注意几个细节:

    1. (ma1cnt):表示最长链个数,如果(ma1cnt>=3)那就表示,我们摧毁某个点x后,只能够让x子树中的教徒不开心,而对于其他所有教徒,他们至少还能够去一条最长链,他们不会不开心。
    2. if(link[y]==ma2&&!id2)id2=y这个if判断中注意(!id2),一开始没加在CF上wa了,因为这样会漏掉ma1==ma2的情况,也即A个数>=2的情况,而加上后,如果存在>=2条的A,我们就可以把id1,id2都赋上正确的树的编号。

    part3中,我们对于每个(不是根节点&&没建修道院)的节点进行分类讨论,然后得出CNT——表示摧毁这个城镇能让多少教徒不开心,然后更新一下答案就可以了。至于分类讨论的细节详见下面注释,可以根据代码画画图自行yy:

    int ma1=0,ma2=0,id1=0,id2=0,ma1cnt=0;
    //part1
    for(int i=0;i<e[root].size();i++){
    	int y=e[root][i].to;
    	if(!cnt[y])continue;
    	if(link[y]>ma1)ma2=ma1,ma1=link[y];
    	else if(link[y]>ma2)ma2=link[y];
    }	
    //part2
    for(int i=0;i<e[root].size();i++){
    	int y=e[root][i].to;
    	if(!cnt[y])continue;
    	if(link[y]==ma1)ma1cnt++,id1=y;
    	if(link[y]==ma2&&!id2)id2=y;
    }
    if(ma1cnt>=3)id1=id2=0;//存在三条最长链
    //part3
    int ret1=0,ret2=0;
    if(!is[root])ret1=m,ret2=1;//因为下面只包含对森林中每棵树中的节点的讨论,故如果能够摧毁根节点,要先单独提出来处理
    for(int i=1;i<=n;i++){
    	if(i==root||is[i])continue;
    	int CNT=cnt[i],belong=id[i];//CNT=cnt[i]:一定能拦截子树中的所有修道院 	
    	if(CNT&&(link[belong]==link[i])&&(nums[belong]==nums[i])){
    		if(belong==id1){
    			if(ma1!=ma2)CNT+=m-cnt[belong];//+=只有一条最长链->还可拦截(除了belong及其子孙)外的所有修道院 
    			else CNT+=cnt[id2];//存在两条最长链,+=只有另一条最长链上的修道院会被拦截 
    		}
    		else if(belong==id2)CNT+=cnt[id1];//1条最长链,1条次长链 
    	}
    	if(CNT>ret1)ret1=CNT,ret2=1;
    	else if(CNT==ret1)ret2++;
    }
    printf("%d %d
    ",ret1,ret2);
    
    

    贴上完整代码

  • 相关阅读:
    73. Set Matrix Zeroes
    289. Game of Live
    212. Word Search II
    79. Word Search
    142. Linked List Cycle II
    141. Linked List Cycle
    287. Find the Duplicate Number
    260. Single Number III
    137. Single Number II
    Oracle EBS中有关Form的触发器的执行顺序
  • 原文地址:https://www.cnblogs.com/Tieechal/p/11232287.html
Copyright © 2011-2022 走看看