#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\n",ret1,ret2);

贴上完整代码

Codeforces-348E Pilgrims的更多相关文章

  1. Codeforces 348E 树的中心点的性质 / 树形DP / 点分治

    题意及思路:http://ydc.blog.uoj.ac/blog/12 在求出树的直径的中心后,以它为根,对于除根以外的所有子树,求出子树中的最大深度,以及多个点的最大深度的lca,因为每个点的最长 ...

  2. python爬虫学习(5) —— 扒一下codeforces题面

    上一次我们拿学校的URP做了个小小的demo.... 其实我们还可以把每个学生的证件照爬下来做成一个证件照校花校草评比 另外也可以写一个物理实验自动选课... 但是出于多种原因,,还是绕开这些敏感话题 ...

  3. 【Codeforces 738D】Sea Battle(贪心)

    http://codeforces.com/contest/738/problem/D Galya is playing one-dimensional Sea Battle on a 1 × n g ...

  4. 【Codeforces 738C】Road to Cinema

    http://codeforces.com/contest/738/problem/C Vasya is currently at a car rental service, and he wants ...

  5. 【Codeforces 738A】Interview with Oleg

    http://codeforces.com/contest/738/problem/A Polycarp has interviewed Oleg and has written the interv ...

  6. CodeForces - 662A Gambling Nim

    http://codeforces.com/problemset/problem/662/A 题目大意: 给定n(n <= 500000)张卡片,每张卡片的两个面都写有数字,每个面都有0.5的概 ...

  7. CodeForces - 274B Zero Tree

    http://codeforces.com/problemset/problem/274/B 题目大意: 给定你一颗树,每个点上有权值. 现在你每次取出这颗树的一颗子树(即点集和边集均是原图的子集的连 ...

  8. CodeForces - 261B Maxim and Restaurant

    http://codeforces.com/problemset/problem/261/B 题目大意:给定n个数a1-an(n<=50,ai<=50),随机打乱后,记Si=a1+a2+a ...

  9. CodeForces - 696B Puzzles

    http://codeforces.com/problemset/problem/696/B 题目大意: 这是一颗有n个点的树,你从根开始游走,每当你第一次到达一个点时,把这个点的权记为(你已经到过不 ...

  10. CodeForces - 148D Bag of mice

    http://codeforces.com/problemset/problem/148/D 题目大意: 原来袋子里有w只白鼠和b只黑鼠 龙和王妃轮流从袋子里抓老鼠.谁先抓到白色老鼠谁就赢. 王妃每次 ...

随机推荐

  1. linux 将子文件夹的文件复制到 当前目录中

    linux 将子文件夹的文件复制到 当前目录中,如 目录结构大概是 -sh |__ db_backup |___ test |____ 2018_01_01_00_00_00 |_____ 2018_ ...

  2. 【JZOJ6376】樱符[完全墨染的樱花]

    description analysis 无向图上任意两点最大流不超过\(2\),说明该图是仙人掌 由于最大流等于最小割,如果一条边在两个环里,断掉两个端点至少需要\(3\)的代价 对于该仙人掌,求两 ...

  3. CSS压缩

    通过W3C 统一验证工具的检测没有错误后,为了提高加载速度和节约空间(相对来说,css量很少的情况下,几乎没啥区别),可以通过css压缩工具把css进行压缩. w3c css压缩 http://too ...

  4. Mybatis笔记 – 入门程序开发

    一.Mybatis开发环境 JDK:jdk_1.7 Eclipse:Oxygen.1 Release (4.7.1) MySQL:MySQL Servr 5.7 1.添加相关ja r包 mybatis ...

  5. PAT甲级——A1128 N Queens Puzzle【20】

    The "eight queens puzzle" is the problem of placing eight chess queens on an 8 chessboard ...

  6. Java 11 发布计划来了,已确定 3个 新特性!!

    Oracle 已经发布了 Java Development Kit 10,下一个版本 JDK 11 也在准备之中了.按照 Java 新的版本发布标准,Java 11 将在 6 个月后到来,现在它还只有 ...

  7. 01_springboot2.x之springboot入门

    1.简介 Spring Boot来简化Spring应用开发,约定大于配置, 去繁从简,just run就能创建一个独立的,产品级别的应用. 优点: 1.简化Spring应用开发的一个框架: 2.整个S ...

  8. 13-5-let和()的作用域

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  9. js 仿微信投诉—引入vue.js,拆分组件为单个js

    效果 页面目录 index.html <!DOCTYPE html > <html> <head> <meta charset="UTF-8&quo ...

  10. Linux 下 cs8900a 的移植说明

    为 cs8900a 建立编译菜单 1. 拷贝到文件 把 cs8900a 的压缩包拷贝到 arm 用户下的 dev_home/localapps/ [arm@localhost localapps]$ ...