#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. java排序,冒泡排序,选择排序,插入排序,快排

    冒泡排序 时间复杂度:O(n^2) 空间复杂度O(1) 稳定性:稳定 比较相邻的元素.如果第一个比第二个大,就交换他们两个. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对.这步做完后,最 ...

  2. hibernate4.3.8的dialect和创建SessionFactory遇到的一些问题

    好久不用hibernat,心里记着的还是hibernate3的标准,今天换成hibernate4.3.8后问题层出不穷啊... 首先是hibernate4.3.8中使用mysql方言时,hiberna ...

  3. Vim: 强大的g

    来源于:http://vim.wikia.com/wiki/Power_of_g 一般格式: :[range]g/pattern/cmd 对range内所有符合pattern的行执行cmd 常见的一些 ...

  4. 编译报错 :The method list(String, Object[]) is ambiguous for the type BaseHibernateDao<M,PK>

    原因:eclipse 的个bug,具体见http://stackoverflow.com/questions/10852923/method-is-ambiguous-for-the-type-but ...

  5. 简单总结Class.forName("").newinstance()和new()以及classLoader.loadClass("")的区别

    文章目录 背景 三种方法简单介绍 Class.forName("").newinstance()方式 new方式 classLoader.loadClass("" ...

  6. day 70 Django基础五之django模型层(二)多表操作

    Django基础五之django模型层(二)多表操作   本节目录 一 创建模型 二 添加表记录 三 基于对象的跨表查询 四 基于双下划线的跨表查询 五 聚合查询.分组查询.F查询和Q查询 六 ORM ...

  7. day 66 Django基础二之URL路由系统

    Django基础二之URL路由系统   本节目录 一 URL配置 二 正则表达式详解 三 分组命名匹配 四 命名URL(别名)和URL反向解析 五 命名空间模式 一 URL配置 Django 1.11 ...

  8. PAT甲级——A1129 Recommendation System【25】

    Recommendation system predicts the preference that a user would give to an item. Now you are asked t ...

  9. JAVA基础_泛型

    什么是泛型 泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉"类型"信息,是程序的运行效率不受影响 ...

  10. Python操作三大主流数据库✍✍✍

    Python操作三大主流数据库 Python 标准数据库接口为 Python DB-API,Python DB-API为开发人员提供了数据库应用编程接口. Python 数据库接口支持非常多的数据库, ...