[BZOJ1912][Apio2010]patrol 巡逻(树上最长链)

=== ===

这里放传送门

=== ===

题解

因为一开始的图是一棵树,所以在不加边的情况下每条边一定会被经过两次,巡逻的代价固定为 ans=2(n1)  。如果在树上加一条边,那么就会有一条链不需要走两次,这条链的起点和终点就是新边的起点和终点。那么如果只能在树上加一条边的话,和这条边构成环的链一定是越长越好。于是找树上最长链就能解决问题。
第一次求最长链的时候可以用dfs也可以DP,这里用了dfs,方法是先从任意一个点dfs找到最远的那个点,再从这个点dfs第二遍,找到另一个最远点,这两个点中间的那条链就是最长的,代价就变为 anslen+1  len  为链的长度。
可是当k=2的时候怎么办呢?题目中要求每条边必须经过一次,也就是说为了满足这个要求,上一次加边后本来可以只被经过一次的某些边可能被”强行“经过两次,也就是说这条链形成的环如果与第一次找到的链有重叠,那么这条重叠的边仍然需要走两遍,相当于没有加也没有减。那么就把上一次选定的链上的每条边边权都赋值为-1,再跑一边最长链就可以了。而这就出现了一个问题就是dfs不适用了,因为出现了负边权。于是这时使用了树形dp,用 f  数组和 g  数组分别存储以结点i开头的最长链和次长链,这两个的和就是经过节点i的最长链。

代码

#include
#include
#include
using namespace std;
int n,k,tot,p[100010],a[200010],w[200010],next[200010],lc,ld,pre[100010],ans,st;
int wer,f[100010],g[100010];
void add(int x,int y,int v){
    a[tot]=y;w[tot]=v;next[tot]=p[x];p[x]=tot++;
}
void dfs(int u,int last,int len){
    for (int i=p[u];i!=-1;i=next[i])
      if (a[i]!=last){
          if (len+w[i]>ld){
              ld=len+w[i];lc=a[i];
          }//找到最长链
          pre[a[i]]=i;
          dfs(a[i],u,len+w[i]);
      }
}
void dp(int u,int last){
    for (int i=p[u];i!=-1;i=next[i])
      if (a[i]!=last){
          int v1,v2;
          dp(a[i],u);
          v1=f[a[i]]+w[i];
          if (v1>f[u]){//判断这个子树的答案应该更新f还是更新g,只能更新一个
              g[u]=f[u];f[u]=v1;
          }else g[u]=max(g[u],v1);
      }
      wer=max(wer,f[u]+g[u]);
}
int main()
{
    memset(p,-1,sizeof(p));
    scanf("%d%d",&n,&k);
    for (int i=1;iint u,v;
        scanf("%d%d",&u,&v);
        add(u,v,1);add(v,u,1);
    }
    ans=2*(n-1);
    dfs(1,0,0);
    memset(pre,0,sizeof(pre));
    ld=0;st=lc;dfs(st,0,0);//两次dfs,第一次的pre数组没有什么意义
    ans=ans-ld+1;
    if (k==2){
        for (int i=lc;i!=st;i=a[pre[i]^1])
          w[pre[i]]=w[pre[i]^1]=-1;//修改最长链上的边权
        wer=-0x7fffffff;dp(1,0);
        ans=ans-wer+1;
    }
    printf("%d\n",ans);
    return 0;
}

偏偏在最后出现的补充说明

DP的时候注意最长链和次长链不能在同一棵子树上。所以一棵子树的答案对于父节点来说要么更新 f  ,要么更新 g  。为了在第二次求最长链时修改树上的边权,第一次求最长链的时候要顺便记录路径,即代码中的 pre  数组。

你可能感兴趣的:(BZOJ,感觉很有趣,DP好难啊)