JZOJ 5496 Tree(点分治+树形dp)

Tree

* Description*

从前有棵树。
找出 k 个点 A 1 , A 2 , · · · , A k 。
∑ k−1
使得 i=1 dis(A i A i+1 ) 最小。

Input

第一行两个正整数 n, k, 表示数的顶点数和需要选出的点个数。
接下来 n − 1 行每行 3 个非负整数 x, y, z, 表示从存在一条从 x 到 y 权值为 z 的边。
1 ≤ k ≤ n。
1 ≤ x, y ≤ n。
1 ≤ z ≤ 10 5 。

Output

一行一个整数, 表示最小的距离和。

Scoring

对于 10% 的数据, n ≤ 10。
对于另外 10% 的数据, k = n。
对于另外 20% 的数据, n ≤ 50。
对于另外 20% 的数据, n ≤ 200。
对于 100% 的数据, n ≤ 3000。


标解是直接dp,但也可以点分治。
首先注意到答案一定是一个树上联通块,那么点分治后只需要考虑包含根的联通块,那么转化成一个树形依赖dp。
另外需要注意到最终的答案在点分治下,可以存在两条从根出发的链只走一次,其他的需要走两次。
F[x][y][0/1] F [ x ] [ y ] [ 0 / 1 ] 表示以x为根,选y个点,是否存在从根出发只走一次的链,
那么对于每个点 F[x][1][0]=2d,F[x][1][1]=d F [ x ] [ 1 ] [ 0 ] = 2 ∗ d , F [ x ] [ 1 ] [ 1 ] = d ,d表示从他父亲到他的边长。
有转移

F[x][y+k][0]=min(F[son][k][0]+F[x][y][0]) F [ x ] [ y + k ] [ 0 ] = m i n ( F [ s o n ] [ k ] [ 0 ] + F [ x ] [ y ] [ 0 ] )

F[x][y+k][1]=min(F[son][k][0]+F[x][y][1],F[son][k][1]+F[x][y][0]d) F [ x ] [ y + k ] [ 1 ] = m i n ( F [ s o n ] [ k ] [ 0 ] + F [ x ] [ y ] [ 1 ] , F [ s o n ] [ k ] [ 1 ] + F [ x ] [ y ] [ 0 ] − d )

最后在根节点做一次背包, G[x][k] G [ x ] [ k ] 表示选了 x x 个点,其中有k条只走一次的链 k=0,1,2 k = 0 , 1 , 2 ,这个直接背包就行。
最后答案取 min(G[m][0],G[m][1],G[m][2]) m i n ( G [ m ] [ 0 ] , G [ m ] [ 1 ] , G [ m ] [ 2 ] )

复杂度很玄学,不知道。但跑得飞快。

这道题最关键的地方是,dp跑for循环的时候,一定只枚举子树size的前缀和,并且不要写 F[x][yk] F [ x ] [ y − k ] F[x][y+k] F [ x ] [ y + k ] 要快的多。具体可以看代码的注释


代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#define N 3005
using namespace std;
int n,m,Min,rt,Ans,si[N],F[N][2][N],G[3][N];
bool mark[N];
int TOT,LA[N],NE[N<<1],EN[N<<1],LE[N<<1];
void ADD(int x,int y,int z)
{
    TOT++;
    EN[TOT]=y;
    LE[TOT]=z;
    NE[TOT]=LA[x];
    LA[x]=TOT;
}
void Gsi(int x,int f)
{
    int i,y;si[x]=1;
    for(i=LA[x];i;i=NE[i])
    {
        y=EN[i];
        if(y==f||mark[y])continue;
        Gsi(y,x);si[x]+=si[y];
    }
}
void Grt(int x,int s,int f)
{
    int i,y,Max=s-si[x];
    for(i=LA[x];i;i=NE[i])
    {
        y=EN[i];
        if(y==f||mark[y])continue;
        Grt(y,s,x);Max=max(Max,si[y]);
    }
    if(Max<Min)Min=Max,rt=x;
}
void DP(int x,int d,int f)
{
    int i,j,k,y,sum=1;
    fill(F[x][0],F[x][0]+si[x]+1,1e9);
    fill(F[x][1],F[x][1]+si[x]+1,1e9);
    F[x][0][1]=2*d;F[x][1][1]=d;
    for(i=LA[x];i;i=NE[i])
    {
        y=EN[i];if(mark[y]||y==f)continue;
        DP(y,LE[i],x);
        if(sum>m)sum=m;
        for(j=sum;j>=1;j--)//跑得慢的写法是for(j=sum+si[y];j>=2;j--)F[x][1][j-k]=min()
        for(k=1;k<=si[y];k++)
        {
            F[x][1][j+k]=min(F[x][1][j+k],F[x][0][j]+F[y][1][k]-d);
            F[x][1][j+k]=min(F[x][1][j+k],F[x][1][j]+F[y][0][k]);
        }
        for(j=sum;j>=1;j--)//同理
        for(k=1;k<=si[y];k++)F[x][0][j+k]=min(F[x][0][j+k],F[x][0][j]+F[y][0][k]);
        sum+=si[y];
    }
}
void Gans(int x)
{
    int i,j,k,p,y,sum=1;
    memset(G,60,sizeof(G));
    G[0][1]=G[1][1]=G[2][1]=0;Gsi(x,0);
    for(i=LA[x];i;i=NE[i])
    {
        y=EN[i];if(mark[y])continue;
        DP(y,LE[i],x);
        if(sum>m)sum=m;
        for(k=sum;k>=1;k--)//同理
        for(p=1;p<=si[y];p++)
        {
            G[2][k+p]=min(G[2][k+p],G[1][k]+F[y][1][p]);
            G[2][k+p]=min(G[2][k+p],G[2][k]+F[y][0][p]);
        }
        for(k=sum;k>=1;k--)//同理
        for(p=1;p<=si[y];p++)
        {
            G[1][k+p]=min(G[1][k+p],G[0][k]+F[y][1][p]);
            G[1][k+p]=min(G[1][k+p],G[1][k]+F[y][0][p]);
        }
        for(k=sum;k>=1;k--)//同理
        for(p=1;p<=si[y];p++)G[0][k+p]=min(G[0][k+p],G[0][k]+F[y][0][p]);
        sum+=si[y];
    }
    Ans=min(Ans,G[0][m]);
    Ans=min(Ans,G[1][m]);
    Ans=min(Ans,G[2][m]);
}
void DC(int x)
{
    int i,y;mark[x]=1;
    Gans(x);
    for(i=LA[x];i;i=NE[i])
    {
        y=EN[i];
        if(mark[y])continue;
        Min=1e9;Gsi(y,x);
        Grt(y,si[y],x);DC(rt);
    }
}
int main()
{
    int i,j,k,x,y,z;
    scanf("%d%d",&n,&m);
    for(i=1;i<n;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        ADD(x,y,z);ADD(y,x,z);
    }
    Ans=1e9;
    Min=1e9;Gsi(1,0);Grt(1,n,0);DC(rt);
    printf("%d",Ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值