bzoj 1509 //1509:[NOI2003]逃学的小孩

bzoj 1509 //1509:[NOI2003]逃学的小孩   //在线测评地址https://www.lydsy.com/JudgeOnline/problem.php?id=1509

更多题解,详见https://blog.csdn.net/mrcrack/article/details/90228694BZOJ刷题记录

//关于树的直径,此文https://blog.csdn.net/forever_dreams/article/details/81051578介绍得不错。

树的直径

方法一:两次bfs(或者dfs)

方法:先从任意一点P出发,找离它最远的点Q,再从点Q出发,找离它最远的点W,W到Q的距离就是是的直径
证明如下:
①若P已经在直径上,根据树的直径的定义可知Q也在直径上且为直径的一个端点
②若P不在直径上,我们用反证法,假设此时WQ不是直径,AB是直径
--->若AB与PQ有交点C,由于P到Q最远,那么PC+CQ>PC+CA,所以CQ>CA,易得CQ+CB>CA+CB,即CQ+CB>AB,与AB是直径矛盾,不成立,如下图(其中AB,PQ不一定是直线,画成直线是为了方便):

bzoj 1509 //1509:[NOI2003]逃学的小孩_第1张图片

(受https://blog.csdn.net/zhanxufeng/article/details/80715185启发,对如下证明略作改进)
--->若AB与PQ没有交点,M为AB上任意一点,N为PQ上任意一点。首先还是NP+NQ>NQ+MN+MB,同时减掉NQ,得NP>MN+MB,(NP+MN>NP>MN+MB>MB)易知NP+MN>MB,所以NP+MN+MA>MB+MA,即NP+MN+MA>AB(AP>AB),与AB是直径矛盾,所以这种情况也不成立,如下图:

bzoj 1509 //1509:[NOI2003]逃学的小孩_第2张图片

 

 

方法二:树形DP

对于每个节点我们要记录两个值:

f1 [ i ] 表示以 i 为根的子树中,i 到叶子结点距离的最大值
f2 [ i ] 表示以 i 为根的子树中,i 到叶子结点距离的次大值
对于一个节点,它到叶子结点距离的最大值和次大致所经过的路径肯定是不一样的
若j是i的儿子,那么(下面的 w [ i ][ j ] 表示 i 到 j 的路径长度):

        若 f1 [ i ] < f1 [ j ] + w [ i ][ j ],f2 [ i ] = f1 [ i ],f1 [ i ] = f1 [ j ] + w [ i ][ j ];
        否则,若 f2 [ i ] < f1 [ j ] + w [ i ][ j ],f2 [ i ] = f1 [ j ] + w [ i ][ j ];

理解:这样做就是,先看能否更新最大值,若能,它的次大值就是原先的最大值,再更新它的最大值;若不能,就看能不能更新次大值,若能,就更新,不能就不管它
这样的话,最后的答案 answer = max { f1 [ i ] + f2 [ i ] }
代码(这是从叶节点到根节点的DP):

方法一:两次BFS求树的直径

53ms / 7.18MB / 1.30KB C++

//1509:[NOI2003]逃学的小孩
//在线测评地址https://www.luogu.org/problem/P4408
//样例1画图后,比较好理解,如何抽象成具体模行呢。
//“可以保证,任两个居住点间有且仅有一条通路”,说明了数据是棵树,无环。
//两次BFS求树的直径
//此文https://blog.csdn.net/qpswwww/article/details/46859293思路不错,摘抄如下
/*
题目大意:

要从一棵树中找出三个点X,Y,Z
,使得min(dis[A][C],dis[B][C])+dis[A][B]
最大,求这个最大值

思路:

可以发现,min里头的两个东西具体取哪个并不重要,或者说点C距离A更近还是距离B更近并不重要。下面给出一个结论:min(dis[A][C],dis[B][C])+dis[A][B]
取最大值时,路径AB是整个树的直径(最长链),通过BFS找出树的直径后,直接枚举点C即可得到最大答案。
*/
//此文https://blog.csdn.net/zctoylm/article/details/80288445代码写得不错。
// Ti ≤ 1000000000,N ≤ 200000,(N-1)*T,long long是必然。
//因用无向图表示,采用SPFA算法,求两点间距离。
//编写过程中,出现一些错误,如下
/*
for(i=1;i<=n;i++)dis[i]=INF;//此处错写成memset(dis,127,sizeof(dis));原数组的是不能被改变的
if(dis[v]>dis[u]+w){//此处错写成if(d[v]>d[u]+w){
                dis[v]=dis[u]+w;//此处错写成d[v]=d[u]+w;
*/
//样例通过,提交84分,测试点2 WA.2019-10-18 17:08
//再次阅读代码发现 2019-10-19 21:43
/*
LL max(LL a,LL b){//此处错写成int max(int a,int b){
    return a>b?a:b;
}
LL min(LL a,LL b){//此处错写成int min(int a,int b){
    return a }
*/
//样例通过,提交84分,测试点2 WA.2019-10-19 21:45
//继续排查,发现LL Max;//此处错写成int Max=0;
//提交80分,这回是测试点6 WA.第一次遇到这种情况。
//继续排查,发现LL Max=0;//此处错写成LL Max;忙中出错。
//提交AC.2019-10-19 21:52   该题若要减少失误,建议全部变量采用long long   2019-10-19 21:57
#include
#include
#define LL long long
#define maxn 200100
#define INF 1e18
LL d[maxn],da[maxn],db[maxn];
int vis[maxn],head[maxn],cnt=0,n,m,q[maxn*10];
struct node{
    int to,next,w;
}e[maxn*2];//无向图
LL max(LL a,LL b){//此处错写成int max(int a,int b){
    return a>b?a:b;
}
LL min(LL a,LL b){//此处错写成int min(int a,int b){
    return a }
void add_edge(int u,int v,int w){//邻接表
    cnt++,e[cnt].to=v,e[cnt].w=w,e[cnt].next=head[u],head[u]=cnt;
}
int spfa(int s,LL dis[]){
    int u,v,b,h,t,w,i;
    LL Max=0;//此处错写成LL Max;忙中出错。//此处错写成int Max=0;
    memset(vis,0,sizeof(vis));
    for(i=1;i<=n;i++)dis[i]=INF;//此处错写成memset(dis,127,sizeof(dis));原数组的是不能被改变的
    h=t=1,q[t]=s,t++,dis[s]=0,vis[s]=1;
    while(h         u=q[h],b=head[u];
        while(b!=-1){
            v=e[b].to,w=e[b].w;
            if(dis[v]>dis[u]+w){//此处错写成if(d[v]>d[u]+w){
                dis[v]=dis[u]+w;//此处错写成d[v]=d[u]+w;
                if(!vis[v])q[t]=v,t++,vis[v]=1;
            }
            b=e[b].next;
        }
        h++,vis[u]=0;
    }
    for(i=1;i<=n;i++)
        if(dis[i]>Max)Max=dis[i],u=i;
    return u;
}
int main(){
    int i,u,v,w,a,b,c;
    LL Max=0,ans;
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        add_edge(u,v,w),add_edge(v,u,w);//无向图
    }
    a=spfa(1,d),b=spfa(a,da),spfa(b,db);
    ans=da[b];
    for(c=1;c<=n;c++)
        Max=max(Max,min(da[c],db[c]));
    printf("%lld\n",ans+Max);
    return 0;
}


方法二:两次dfs求树的直径

bzoj 1509

10988 kb 1476 ms C++/Edit 1326 B

洛谷58ms / 9.66MB / 996B C++
//两次dfs,此文https://blog.csdn.net/mmh2000/article/details/68957655代码写得不错。
//思路同方法一中的 两次bfs
//样例通过,提交AC.2019-10-20 14:26  测评完成,发生在14:32,也即花了6分钟。
//看了效率,与两次bfs,难分伯仲。
#include
#include
#define LL long long
#define maxn 200100
int head[maxn],cnt=0,n,m;
LL d[maxn],da[maxn],db[maxn];
struct node{
    int to,next,w;
}e[maxn*2];
LL max(LL a,LL b){
    return a>b?a:b;
}
LL min(LL a,LL b){
    return a }
void add_edge(int u,int v,int w){
    cnt++,e[cnt].to=v,e[cnt].w=w,e[cnt].next=head[u],head[u]=cnt;
}
void dfs(int u,int fa,LL d[]){
    int b,v,w;
    for(b=head[u];b!=-1;b=e[b].next){
        v=e[b].to,w=e[b].w;
        if(v!=fa)d[v]=d[u]+w,dfs(v,u,d);
    }
}
int main(){
    int i,a,b,c,u,v,w;
    LL Max,ans;
    memset(head,-1,sizeof(head));//漏了此句,查了5分钟
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        add_edge(u,v,w),add_edge(v,u,w);//无向图
    }
    
    d[1]=0,Max=0;
    dfs(1,-1,d);
    for(i=1;i<=n;i++)
        if(d[i]>Max)Max=d[i],a=i;
    
    da[a]=0,Max=0;
    dfs(a,-1,da);
    for(i=1;i<=n;i++)
        if(da[i]>Max)Max=da[i],b=i;
    
    ans=da[b];
    
    db[b]=0;
    dfs(b,-1,db);
    
    Max=0;
    for(c=1;c<=n;c++)
        Max=max(Max,min(da[c],db[c]));
    printf("%lld\n",ans+Max);
    return 0;
}

方法三:树形dp

此文https://wenku.baidu.com/view/43d7df7cba68a98271fe910ef12d2af90242a8f3.html?from=search分析得不错,摘抄如下:

bzoj 1509 //1509:[NOI2003]逃学的小孩_第3张图片

bzoj 1509 //1509:[NOI2003]逃学的小孩_第4张图片

bzoj 1509 //1509:[NOI2003]逃学的小孩_第5张图片

bzoj 1509 //1509:[NOI2003]逃学的小孩_第6张图片

bzoj 1509 //1509:[NOI2003]逃学的小孩_第7张图片

此文https://blog.csdn.net/gauss_acm/article/details/40625147思路不错,摘抄如下:

/*

这题应该说是树形dp中难度较大的题了。具体参考陈瑜希的论文。

主要思想是通过两遍dfs。第一遍任意选取一个节点作为根,然后求得每个节点子树中前3远的距离。

第二遍dfs是对每个节点求其到其父亲节点引出来的其他节点的最远距离,重新调整每个节点的前3远的距离。

并且第二遍dfs有两个步骤,前一个步骤保证搜索的方向是层次的,这样就能够保证子树更新距离时,父亲节点已更新完毕。

然后为什么父亲节点会引出一条路径。其实是因为每个分叉结点的前3远节点在不同的三颗子树(以该节点为根),也就是三颗子树交于该点,而过该点从其父亲方向过来的只可能有一条,如果大于1条,则说明三颗子树交于多个点组成的线段。

然后可能有人会有疑问?如果仅有一条树链,那么三点不是不交于1点,但是因为我们枚举了所有分叉点,对于这条链上的中间那个点作为分叉点,并且按照论文中转换,也就是添加边权为0的节点,那么就符合题意了,这个在代码里赋初值时是有体现的。但可能有人又有疑问了,其他点作为分叉点,最优解不就不交于1点了。

没错,但是此时根据代码,这些点只会求得以三颗子树交于该点的最优解,因此这些点得不到最优解。但是因为枚举了所有点作为分叉点,所以必然会在以那个中间点作为分叉点时,得到最优解。

所以这些完全不需要担心。。。但是如果你有这些顾虑,那说明你思考问题相当严密,值得鼓励。

*/

//此文https://blog.csdn.net/fouzhe/article/details/52515673代码写得不错。2019-10-20 21:15

 

你可能感兴趣的:(跟着大佬学算法)