[模拟赛10.12] 老大 (二分/树的直径/树形dp)

[模拟赛10.12] 老大

题目描述

因为 OB 今年拿下 4 块金牌,学校赞助扩建劳模办公室为劳模办公室群,为了体现 OI 的特色,办公室群被设计成了树形(n 个点 n ? 1 条边的无向连通图),由于新建的办公室太大以至于要将奖杯要分放在两个不同的地方以便同学们丢硬币进去开光,OB 想请你帮帮他看看奖杯放在哪两个办公室使得在任意一个在劳模办公室做题的小朋友能最快地找到奖杯来开光。
一句话题意:给出一个 n 个点的树,在两个合适且不同的点放上奖杯,使得每个点到最近的奖杯距离最大值最小。

输入

第一行,一个整数 n。
接下来的 n ? 1 行,每行两个数 x y

输出

一个数,表示最小的最大距离。

样例输入

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

样例输出

2

提示

对于前 60% 的数据,n ≤ 100。
对于前 80% 的数据,n ≤ 2000。
对于 80% 的数据,保证树的形态随机。
对于 100% 的数据,保证 3 ≤ n ≤ 200000。

Solution

这道题解决方法非常多,然而博主蒟蒻只会\(O(n\log n)\)的做法

那么这两个奖杯到底要放哪里呢?可以证明一定在树的直径上
简易的讲一下
这就要说到树的直径的性质:树的直径是树上最长的一条路径,且树上任意一个点距它距离最远的点一定是树的直径的一个端点

假设奖杯在树的直径上能满足条件,那么我们最起码要保证它一定能覆盖到直径的至少一个端点,否则肯定不满足条件,既然它能满足端点,那么一定能满足直径上的点的子树中的节点,除非一个点子树中最深深度比它离直径端点的距离还远,但这又违反了直径的性质,故假设成立

知道了这两个点在直径上,怎么知道它们距离直径的距离呢?因为这两个点离直径端点的距离肯定是在满足题意的前提下距离最大的,我们要让这个距离最小,可以二分这个距离,然后去\(O(n)\)验证

怎么验证,就是我开始说的性质,首先找到这两个节点,然后找两个节点之间的那一段区间的每一颗子树,看不在直径上的节点的最大深度是不是超过了我们二分的这个mid

考场上匆忙打出来的代码,有点丑陋,将就着看吧~~

听说这道题还可以树形dp\(O(n)\)做,在这里贴一下题解说的各种做法

\(3.1\ 60\% O(n^3 )\)
\(n^2\)枚举两个奖杯位置,再\(O(n)\)扫一遍看看每个位置离最近奖杯最远是多少。

\(3.2\ 80\% O(n^2)\)
考虑两个奖杯管辖的区域必定有一个边界,我们枚举这个边界,也就是一条边,其中一部分是子树,一部分是子树外,我们只要分别求出另外两个树的直径。

\(3.3\) 树形态随机
期望树的直径很短,两个奖杯都在直径上枚举。

\(3.4\ 100\%\) 二分答案1 \(O(nlogn)\)
奖杯在直径上,二分答案后取离直径上离端点距离答案的点,遍历 check 一遍。

\(3.5\ 100\%\) 二分答案 2 \(O(nlogn)\)
随便提一个节点为根,二分答案,深度最深的节点一定要被照顾到,所以最深的点往上跳答案层即可,和其距离答案以内的点都删掉,再做一次。
此法可以拓展到 k 个奖杯,由皮皮轩友情提供。

\(3.6\ 100\%\) 树形dp\(\ O(n)\)
在 80 分的基础上用树形 dp,记下每个点向下前三长和向上一格后不回该子树最长的路径长度。子树内直径是前两长的和与该子树各个子树直径取 max;子树外直径是父节点向上一格后不回该子树最长的路径长度,前两长不进入该子树的向下最长路径这三条取前两长加起来与父节点以上的答案取 max。

Code

#include<bits/stdc++.h>
#define rg register
#define il inline
#define Min(a,b) (a)<(b)?(a):(b)
#define Max(a,b) (a)>(b)?(a):(b)
#define lol long long
#define in(i) (i=read())
using namespace std;

const int N=2e5+10;

int read() {
    int ans=0,f=1; char i=getchar();
    while(i<'0' || i>'9') {if(i=='-') f=-1; i=getchar();}
    while(i>='0' && i<='9') ans=(ans<<1)+(ans<<3)+i-'0',i=getchar();
    return ans*=f;
}

int n,cur,s,t;
int to[N<<1],nex[N<<1],head[N];
int dis[N],f[N],son[N],vis[N],dep[N];

void add(int a,int b) {
    to[++cur]=b,nex[cur]=head[a],head[a]=cur;
    to[++cur]=a,nex[cur]=head[b],head[b]=cur;
}

void dfs(int u,int fa) {
    f[u]=fa;
    for(int i=head[u];i;i=nex[i]) {
        if(to[i]==fa) continue;
        dis[to[i]]=dis[u]+1;
        dfs(to[i],u);
    }
}

int dfs2(int u,int ans=1) {
    dep[u]=dis[u];
    for(int i=head[u];i;i=nex[i]) {
        if(to[i]==f[u] || vis[to[i]]) continue;
        dfs2(to[i]); dep[u]=max(dep[u],dep[to[i]]);
    }return ans;
}

bool check(int mid) {
    int a=s,b=t;
    while(a!=t) {
        if(dis[a]-dis[s]==mid) break;
        a=son[a];
    }
    while(b!=s) {
        if(dis[t]-dis[b]==mid) break;
        b=f[b];
    }
    if(dis[b]-dis[a]>2*mid) return 0;
    int ll=a,rr=b;
    while(a!=b && b) {
        dfs2(b); int AQ=dep[b]-dis[b];
        if(min(dis[b]-dis[ll],dis[rr]-dis[b])+AQ>mid) return 0;
        b=f[b];
    }
    dfs2(b); int AQ=dep[b]-dis[b];
    if(min(dis[b]-dis[ll],dis[rr]-dis[b])+AQ>mid) return 0;
    return 1;
}

int main()
{
    //freopen("ob.in","r",stdin);
    //freopen("ob.out","w",stdout);
    in(n);
    for(int i=1,a,b;i<n;i++)
        in(a),in(b),add(a,b);
    dfs(1,0);

    for(int i=1;i<=n;i++) if(dis[i]>dis[s]) s=i;
    memset(dis,0,sizeof(dis)); dfs(s,0);
    for(int i=1;i<=n;i++) if(dis[i]>dis[t]) t=i;

    int id=t; while(id) vis[id]=1,son[f[id]]=id,id=f[id];
    vis[s]=vis[t]=1;
    int l=0,r=dis[t];
    while(l<r) {
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }cout<<r<<endl;
}

原文地址:https://www.cnblogs.com/real-l/p/9778861.html

时间: 2024-11-10 13:19:13

[模拟赛10.12] 老大 (二分/树的直径/树形dp)的相关文章

2014 Super Training #9 E Destroy --树的直径+树形DP

原题: ZOJ 3684 http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3684 题意: 给你一棵树,树的根是树的中心(到其他点的最远距离最小).现在你要破坏所有叶子节点到根节点的连通,每条边破坏都需要一定能量.你有一个能量为power的武器,能破坏能量小于等于power的任何路.求最少需要的power. 解法参考博客:http://blog.csdn.net/gzh1992n/article/details/86511

POJ 1849 Two(树的直径--树形DP)(好题)

大致题意:在某个点派出两个点去遍历所有的边,花费为边的权值,求最少的花费 思路:这题关键好在这个模型和最长路模型之间的转换,可以转换得到,所有边遍历了两遍的总花费减去最长路的花费就是本题的答案,要思考,而且答案和派出时的起点无关 求最长路两遍dfs或bfs即可,从任意点bfs一遍找到最长路的一个终点,再从这个终点bfs找到起点 //1032K 79MS C++ 1455B #include<cstdio> #include<iostream> #include<cstring

@省选模拟赛03/16 - T3@ 超级树

目录 @description@ @solution@ @accepted code@ @details@ @description@ 一棵 k-超级树(k-SuperTree) 可按如下方法得到:取一棵深度为 k 的满二叉树,对每个节点向它的所有祖先连边(如果这条边不存在的话). 例如,下面是一个 4-超级树: 请统计一棵 k-超级树 中有多少条不同的简单有向路径,对 mod 取模. input 一行两整数 k, mod. output 一行一整数表示答案. example input1: 2

zoj 3820 Building Fire Stations (二分+树的直径)

Building Fire Stations Time Limit: 5 Seconds      Memory Limit: 131072 KB      Special Judge Marjar University is a beautiful and peaceful place. There are N buildings and N - 1 bidirectional roads in the campus. These buildings are connected by road

「10.12」木板(数学)&#183;打扫卫生(神仙DP)

A. 木板 一个很简单的数学题,简单推一下就好,路丽姐姐教你学数学. 将式子化出我们发现只需求出$i\times i/n$的个数 那么我们将$n$质因数分解,可知因子个数 为了整除$n$,令$i==\sqrt{n\times k} $,我们需要让$k$含有$n$中奇数个数的因子 然后同时还可以有其他的平方因子,直接爆求即可. 思路积累: 1.对于求$i\times i/n$可以从$n$的因子上下手 B. 打扫卫生 一个$DP$的大神题,考场被各种剪枝cao过 $ \%\%\%\%\%kx,Dua

[HDOJ2196]Computer (树直径 树形DP)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2196 给一棵树,求树上各点到某点的距离中最长的距离.注意每个点都要求. 和普通求树的直径不一样,要求每一个点,点的数量是10000因此每一个点都跑一次dfs就会超时.因此先随便dfs一个点,找到一个点后以此点为原点再找距离它最远的点,再找一次即可找到树直径两端的点. 1 #include <algorithm> 2 #include <iostream> 3 #include <

POJ 1655 BalanceAct 3107 Godfather (树的重心)(树形DP)

参考网址:http://blog.csdn.net/acdreamers/article/details/16905653 树的重心的定义: 树的重心也叫树的质心.找到一个点,其所有的子树中最大的子树节点数最少,那么这个点就是这棵树的重心,删去重心后,生成的多棵树尽可能平衡. 通常利用树形DP找重心: BalanceAct: http://poj.org/problem?id=1655 题意:给定一棵树,求树的重心的编号以及重心删除后得到的最大子树的节点个数size,如果size相同就选取编号最

POJ 1655 Balancing Act[树的重心/树形dp]

Balancing Act 时限:1000ms Description Consider a tree T with N (1 <= N <= 20,000) nodes numbered 1...N. Deleting any node from the tree yields a forest: a collection of one or more trees. Define the balance of a node to be the size of the largest tree

HDOJ 5293 Tree chain problem LCA+树链剖分+树形DP

[题意] 给定一颗树上的几条链和每条链的权值,求能取出的不含有公共节点的链的最大权值.... [解] 预处理每条链的lca 树形DP, d[i]表示取到这个节点时可以得到的最大值 , sum[i]=sigma( d[k] | k 是i的子节点) 如果不取i  d[i]=sum[i] 如果取i , e是lca为i的链则 d[i]=max(d[i],e的权值+sigma(sum[k])-sigma(d[k]))  k为树链上的点 可以用树链剖分+树装数组在nlogn的时间复杂度内求链上的值 Tree