BZOJ 1912 [Apio2010]patrol 巡逻

来源:互联网 发布:在淘宝上买摩托车 编辑:程序博客网 时间:2024/05/17 00:51

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

然后百度自学了一波
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);}