Codeforces-348E Pilgrims
#4342. CF348 Pilgrims
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。其中注意几个细节:
- \(ma1cnt\):表示最长链个数,如果\(ma1cnt>=3\)那就表示,我们摧毁某个点x后,只能够让x子树中的教徒不开心,而对于其他所有教徒,他们至少还能够去一条最长链,他们不会不开心。
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\n",ret1,ret2);
Codeforces-348E Pilgrims的更多相关文章
- Codeforces 348E 树的中心点的性质 / 树形DP / 点分治
题意及思路:http://ydc.blog.uoj.ac/blog/12 在求出树的直径的中心后,以它为根,对于除根以外的所有子树,求出子树中的最大深度,以及多个点的最大深度的lca,因为每个点的最长 ...
- python爬虫学习(5) —— 扒一下codeforces题面
上一次我们拿学校的URP做了个小小的demo.... 其实我们还可以把每个学生的证件照爬下来做成一个证件照校花校草评比 另外也可以写一个物理实验自动选课... 但是出于多种原因,,还是绕开这些敏感话题 ...
- 【Codeforces 738D】Sea Battle(贪心)
http://codeforces.com/contest/738/problem/D Galya is playing one-dimensional Sea Battle on a 1 × n g ...
- 【Codeforces 738C】Road to Cinema
http://codeforces.com/contest/738/problem/C Vasya is currently at a car rental service, and he wants ...
- 【Codeforces 738A】Interview with Oleg
http://codeforces.com/contest/738/problem/A Polycarp has interviewed Oleg and has written the interv ...
- CodeForces - 662A Gambling Nim
http://codeforces.com/problemset/problem/662/A 题目大意: 给定n(n <= 500000)张卡片,每张卡片的两个面都写有数字,每个面都有0.5的概 ...
- CodeForces - 274B Zero Tree
http://codeforces.com/problemset/problem/274/B 题目大意: 给定你一颗树,每个点上有权值. 现在你每次取出这颗树的一颗子树(即点集和边集均是原图的子集的连 ...
- CodeForces - 261B Maxim and Restaurant
http://codeforces.com/problemset/problem/261/B 题目大意:给定n个数a1-an(n<=50,ai<=50),随机打乱后,记Si=a1+a2+a ...
- CodeForces - 696B Puzzles
http://codeforces.com/problemset/problem/696/B 题目大意: 这是一颗有n个点的树,你从根开始游走,每当你第一次到达一个点时,把这个点的权记为(你已经到过不 ...
- CodeForces - 148D Bag of mice
http://codeforces.com/problemset/problem/148/D 题目大意: 原来袋子里有w只白鼠和b只黑鼠 龙和王妃轮流从袋子里抓老鼠.谁先抓到白色老鼠谁就赢. 王妃每次 ...
随机推荐
- git统计项目中成员代码量
查看git上个人代码量 git log --author="username" --pretty=tformat: --numstat | awk '{ add += $1; su ...
- luoguP2580 于是他错误的点名开始了 [Trie]
题目背景 XS中学化学竞赛组教练是一个酷爱炉石的人. 他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛CON900). ...
- LUOGU P4159 [SCOI2009]迷路(矩阵乘法)
传送门 解题思路 以前bpw讲过的一道题,顺便复习一下矩阵乘法.做法就是拆点,把每个点拆成\(9\)个点,然后挨个连边.之后若\(i\)与\(j\)之间的边长度为\(x\),就让\(i\)的第\(x\ ...
- DNS的解析过程
1.什么是DNS 在互联网上,唯一标识一台计算机的是IP地址,但是IP地址不方便记忆,通过一个域名对应一个IP地址,来达到找到IP地址的目的,那么DNS就是将域名转换成IP地址的过程. 2.DNS查询 ...
- #define SYSTEMSERVICE(_func) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_func+1) 这
这个跟KeServiceDescriptorTable的结构有关 下面是KeServiceDescriptorTable的结构定义 KeServiceDescriptorTabletypedef st ...
- selector是在文件夹drawable中进行定义的xml文件转载 https://www.cnblogs.com/fx2008/p/3157040.html
获取Drawable对象: Resources res = mContext.getResources(); Drawable myImage = res.getDrawable(R.drawable ...
- VI/VIM 无法使用系统剪贴板(clipboard)
来自: http://www.bubuko.com/infodetail-469867.html vim 系统剪贴板 "+y 复制到系统剪切板 "+p 把系统粘贴板里的内容粘贴到v ...
- HDU-6375-度度熊学队列-双端队列deque/list
度度熊正在学习双端队列,他对其翻转和合并产生了很大的兴趣. 初始时有 NN 个空的双端队列(编号为 11 到 NN ),你要支持度度熊的 QQ 次操作. ①11 uu ww valval 在编号为 u ...
- typeerror: __init__() missing 2 required positional arguments: 'inputs' and 'outputs'
1 问题描述 使用下边这条命令去检查 TensorFlow Object Detection API是否正确安装: python object_detection\builders\model_bui ...
- JS完美运动框架【利用了Json】
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...