BZOJ 1912 [Apio2010]patrol 巡逻 - 树形DP(树的直径)

一眼看出了是求树的直径,然而没学过可怎么破。。。

然后百度自学了一波
http://www.cnblogs.com/wuyiqi/archive/2012/04/08/2437424.html

大概方法就是任找一个点,找离他最远的点,此点必为直径的端点,然后以此为基准再找一个离他最远的点,连成的链即树的直径。(证明见上地址)

k=0,观察到每条边必走两遍,无关于出发点

k=1即一个裸的树的直径,原因是图中每个边都至少要走两次,加一条路可以少走一遍,而多加这条路的cost(1),设直径为len1,那么ans1=2(n-1)-len1+1

k=2,则在k=1的基础上考虑。再求一条次长链len2,最长链与次长链相交,那么若直接ans1-len2+1必然是错的。因为通过新建路,本来需要往返的边可以省去一次回去的花费,而一段链被跳过两次,则意味着根本没有被遍历到,于是每条边需要再加上两遍,减两遍加两遍,二者相抵,即第二次需要将两次相重的再加回来。而不能先计算再加,因为有可能加的值(即相重的边)会很大,于是先进行等效的-1操作,将第一次选的链上所有边权改为-1,再找次长链。
而这里有一个问题,即上方给出的树的直径寻法针对的是正权(证明的条件是加边则必然权会更大),而负权就会很尴尬了。。。

其实搞一个树形DP就好了,每个节点记一个最长链和次长链,二者通过lca相连,每次记录一下,DP完以后ans2=2(n-1)-len1+1-len2+1即可

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn=100005;

struct edge
{
    int to,next,val;
}e[maxn<<1];

int cnt=1,n,k,maxi,key,xx,yy;
int head[maxn],son1[maxn],son2[maxn];

void insert(int a,int b)
{
    e[++cnt].to=b;e[cnt].val=1;e[cnt].next=head[a];head[a]=cnt;
}
int dfs(int x,int fa)
{
    int max1,max2;
    max1=max2=0;//自己到自己起码有一条0的路 
    son1[x]=son2[x]=x;
    for(int i=head[x];i;i=e[i].next)if(e[i].to!=fa)
    {
        int now=e[i].val+dfs(e[i].to,x);
        if(now>max1)
        {
            max2=max1;
            max1=now;
            son2[x]=son1[x];
            son1[x]=son1[e[i].to];
        }
        else if(now>max2)
        {
            max2=now;
            son2[x]=son1[e[i].to];
        } 
    }
    if(max1+max2>maxi)
    {
        maxi=max1+max2;
        xx=son1[x];
        yy=son2[x];
    }
    return max1;
}
void color(int x,int fa,int fin)
{
    static bool flag=true;
    for(int i=head[x];i&&flag;i=e[i].next)if(e[i].to!=fa)
    {
        e[i].val=e[i^1].val=-1;
        if(e[i].to==fin)flag=false;
        if(!flag)return;
        color(e[i].to,x,fin);
        if(!flag)return;
        e[i].val=e[i^1].val=1;
    }
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1,u,v;i<n;i++)
        scanf("%d%d",&u,&v),
        insert(u,v),
        insert(v,u);
    maxi=0;
    dfs(1,-1);int len1=maxi;
    if(k==1){printf("%d",2*(n-1)-len1+1);return 0;}
    color(xx,-1,yy);
    maxi=0;
    dfs(1,-1);int len2=maxi;
    printf("%d",2*(n-1)-len1+1-len2+1);
    return 0;
}

k=1的情况:树的直径求法

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<algorithm>

using namespace std;

const int maxn=100005;

struct edge
{
    int to,next,val;
}e[maxn<<1];

int cnt=1,n,k,maxi,key,xx,yy;
int head[maxn],dist[maxn];

void insert(int a,int b)
{
    e[++cnt].to=b;e[cnt].val=1;e[cnt].next=head[a];head[a]=cnt;
}
void dfs(int x,int fa)
{
    for(int i=head[x];i;i=e[i].next)if(e[i].to!=fa)
    {
        dist[e[i].to]=dist[x]+e[i].val;
        if(dist[e[i].to]>maxi)key=e[i].to,maxi=dist[e[i].to];
        dfs(e[i].to,x);
    }
}
int length()
{
    maxi=-0x3f3f3f3f;
    memset(dist,0,sizeof dist);
    dfs(1,-1);
    memset(dist,0,sizeof dist);
    xx=key;
    maxi=-0x3f3f3f3f;
    dfs(key,-1);
    yy=key;
}
int main()
{
    scanf("%d%d",&n,&k);
    for(int i=1,u,v;i<n;i++)
        scanf("%d%d",&u,&v),
        insert(u,v),
        insert(v,u);
    length();int len1=maxi;
    printf("%d",2*(n-1)-len1+1-len2+1);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值