zoukankan      html  css  js  c++  java
  • 洛谷 4383 [八省联考2018]林克卡特树lct——树形DP+带权二分

    题目:https://www.luogu.org/problemnew/show/P4383

    关于带权二分:https://www.cnblogs.com/flashhu/p/9480669.html

    自己只能想到 “如果把负边看作不存在,那么分出的连通块的直径一定可以被整个连进最终路径里”。然后就不知道连通块不是恰好 K+1 个怎么办,且也不知道是不是对的……

    原来可以直接把问题看成 “选出恰好 K+1 条不相交路径” 。这样也考虑到了 “恰好 K 条” 的限制,并且好像挺对的。

    结果自己还是不太会……看题解发现原来只需记录 [ 0 / 1 / 2 ] 表示 “未连边” 、 “和孩子连了一条边” 、 “和孩子连了两条边” 就能转移了。

    自己把 [ 0 ] 视作 “不向上传” ,那么 return 的时候 [ 0 ] 就可以对 [ 1 ] 、 [ 2 ] 取 max 了。

    在 “向上传” 的过程中, [ 2 ] 是没用的。之所以要记录,是为了让 “ [ 1 ] + [ 1 ] ” 的情况不要转移到 [ 0 ] ,这样当前点就不会匹配很多路径了。

    于是可以 n*K2 DP。自己写了一下只得了 35 分……

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    ll Mx(ll a,ll b){return a>b?a:b;}
    ll Mn(ll a,ll b){return a<b?a:b;}
    const int N=3e5+5,M=105; const ll INF=3e11+5;
    int n,m,hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1];
    int siz[N]; ll dp[N][3][M];
    void add(int x,int y,int z)
    {to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;}
    void cz(int cr,int i,int j,ll s)
    {dp[cr][i][j]=Mx(dp[cr][i][j],s);}
    void dfs(int cr,int fa)
    {
      for(int i=0;i<3;i++)
        for(int j=0;j<=m;j++) dp[cr][i][j]=-INF;
      dp[cr][0][0]=0; siz[cr]=1;
      for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=fa)
          {
        dfs(v,cr); siz[cr]+=siz[v];
        for(int j=Mn(siz[cr],m);j>=0;j--)
          for(int k=0;k<=j&&k<=siz[v];k++)
            {
              cz(cr,2,j,dp[cr][2][j-k]+dp[v][0][k]);
              if(k)cz(cr,2,j,dp[cr][1][j-k+1]+dp[v][1][k]+w[i]);
              cz(cr,1,j,Mx(dp[cr][0][j-k]+dp[v][1][k]+w[i],
                   dp[cr][1][j-k]+dp[v][0][k]));
              cz(cr,0,j,dp[cr][0][j-k]+dp[v][0][k]);
            }
          }
      for(int j=0;j<=m;j++)
        dp[cr][0][j]=Mx(dp[cr][0][j],Mx(dp[cr][1][j],dp[cr][2][j]));
      dp[cr][1][1]=Mx(dp[cr][1][1],0);
    }
    int main()
    {
      n=rdn();m=rdn()+1;
      for(int i=1,u,v,z;i<n;i++)
        {
          u=rdn();v=rdn();z=rdn();
          add(u,v,z);add(v,u,z);
        }
      dfs(1,0);
      printf("%lld
    ",dp[1][0][m]);
      return 0;
    }
    View Code

    可以用带权二分的知识来优化。

    随着 K 的增加,答案是先增大再减小的。可以感性理解。

    所以就可以带权二分。二分选一条路径额外带来的收益(可为负),然后不限制路径条数地 DP ,只是附带着记录一下选了几条路。最后看选出来的路径如果多于 K+1 条,说明选一条路径的收益太高;不然说明收益太低。当不看路径条数地 DP 的最优解恰好选了 K+1 条路径时就可以退出了。答案就是现在这个最优解减去 “路径条数 * mid” 。

    当路径条数不同但最优解相同地时候,可能二分不到 “恰好 K+1 条路径” 。所以 DP 的时候设第二关键字为路径条数,比如设成值相等时路径越少越好,那么最优解附带的路径条数 <= K+1 的时候也可能就是答案。所以认为答案是二分到的 “最大的 <= K+1 条路径” 的解即可。

    关于二分的范围,自己是让 l = 所有正边权之和 , r = 边权最大值。然后 l-- , r++ 。

      想让路径选得最少,考虑选路径的最优情况就是把所有正边权都选上了,所以让 l 为这个值减1,这样最优就是一条边也不选;

      想让路径选得最多,即每个点自己是一条路径。如果不是的话,就是在这个基础上把两个点连在一条路径里,好处是得到一个边权;所以让 r 是最大的边权加1,这样最优就是每个点自己是一条路径。

     DP 的转移和暴力一样。

    然后调了很久。终于发现自己把 “新增一条路径” 放在 “从孩子转移过来” 之后。这样的话 “以当前点为一个端点的路径” 就不能继承上 “在当前点子树(不含当前点)中完结的路径” 的贡献了!

    发现自己的 n*K2 DP 也在这个地方写错了。怪不得是 WA 而不是 TLE ……

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    ll Mn(ll a,ll b){return a<b?a:b;}
    ll Mx(ll a,ll b){return a>b?a:b;}
    const int N=3e5+5;ll INF=3e11+5;
    int n,m,hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1];
    ll dp[N][3],mid; int f[N][3];
    void add(int x,int y,int z)
    {to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;}
    void cz(int cr,int i,ll w0,int f0,ll w1,int f1)
    {
      if(w0==w1) dp[cr][i]=w0,f[cr][i]=Mn(f0,f1);//Mn
      else if(w0>w1)dp[cr][i]=w0,f[cr][i]=f0;
      else dp[cr][i]=w1,f[cr][i]=f1;
    }
    void dfs(int cr,int fa)
    {
      /*dp[cr][0]=0; dp[cr][1]=dp[cr][2]=-INF;
        f[cr][0]=0; f[cr][1]=0; f[cr][2]=0;*/
      dp[cr][0]=0; dp[cr][1]=mid; dp[cr][2]=-INF;
      f[cr][0]=0; f[cr][1]=1; f[cr][2]=0;
      for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=fa)
          {
        dfs(v,cr);
        cz(cr,2,dp[cr][1]+dp[v][1]+w[i]-mid,f[cr][1]+f[v][1]-1,
           dp[cr][2]+dp[v][0],f[cr][2]+f[v][0]);
        cz(cr,1,dp[cr][1]+dp[v][0],f[cr][1]+f[v][0],
           dp[cr][0]+dp[v][1]+w[i],f[cr][0]+f[v][1]);
        dp[cr][0]+=dp[v][0];
        f[cr][0]+=f[v][0];
          }
      cz(cr,0,dp[cr][0],f[cr][0],dp[cr][2],f[cr][2]);
      cz(cr,0,dp[cr][0],f[cr][0],dp[cr][1],f[cr][1]);
      //cz(cr,1,dp[cr][1],f[cr][1],mid,1);//new
    }
    int main()
    {
      n=rdn();m=rdn()+1;ll l=0,r=-INF;
      for(int i=1,u,v,z;i<n;i++)
        {
          u=rdn();v=rdn();z=rdn();
          add(u,v,z); add(v,u,z);
          if(z>=0)l-=z; r=Mx(r,z);
        }
      l--; r++; ll ans,ret;
      while(l<=r)
        {
          mid=l+r>>1;
          dfs(1,0);
          if(f[1][0]<=m)
        {
          ans=dp[1][0];ret=mid;l=mid+1;
          if(f[1][0]==m)break;
        }
          else r=mid-1;
        }
      printf("%lld
    ",ans-ret*m);
      return 0;
    }
  • 相关阅读:
    Android官方命令深入分析之bmgr
    Android官方命令深入分析之AVD Manager
    Android 官方命令深入分析之android
    token的设置与获取
    SpringBoot使用Redis共享用户session信息
    thymeleaf资源加载问题(从Controller跳转)
    ajax传递数组,后台更新
    BootStrap表单验证用户名重复
    hadoop3.x.x错误解决
    Hadoop安装
  • 原文地址:https://www.cnblogs.com/Narh/p/10578734.html
Copyright © 2011-2022 走看看