hdu5148 Cities

求一棵n点树的k个点,使得任意两点距离和最小。

关键字:树形分组背包、换角度求和

原题要求求任意两点的距离之和,不易处理,因此我们可以从边的角度,计算每条边计算了多少次。一条边<u,v>可以将一棵树分为两部分,每部分有x,k-x个点,则该边被计算了2*x*(k-x)次。因此,该边的贡献是2*x*(k-x)*w<u,v>,若u是v的父亲,且已知v中x条边对整体的最小贡献,则可以通过分组背包模型解决该问题。dp[u][k][i]:u节点遍历到前m个孩子节点时,点容量为i的最小距离和。每个孩子可以占据1,2...i个点和贡献相应的距离。dp[u][m][i]={dp[u][m-1][i],dp[u][m-1][i-j]+(dp[v][j]+2*w<u,v>*(k-j)*j)}

完全背包模型:一共有n件物品,和容量为v的背包。第i件物品的容量是Ci,价值是Wi。背包被分为k组,每组至多选1件物品。求一种选择方案,使得物品总容量不超过背包容量,且价值最大。

状态转移方程:F [k; v] = max{ F [k - 1; v]; F [k - 1; v - Ci ] + Wi | item i ∈ group k}

伪代码:

for k  <—1 to K 枚举所有的组 (在树中即为每个子节点)
for v <— V to 0 枚举所有的容量 从大到小,保证每组仅有一个进入背包(在树中即为每个孩子仅进入一次)
for all item i in group k 每组选择一件物品(每个子节点进行一次选择)
F [v]  = max{ F [v], F [v - Ci ] + Wi }

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<map>
#include<vector>
#include<queue>
#define ll long long
#define sf scanf
#define pf printf
#define maxn 10000
#define inf 0x3f3f3f
#define INF 1ll<<60
#define mem(a,b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
using namespace std;


int t,n,k;
struct Edge{
    int to,next;
    ll w;
}edge[maxn];
int head[maxn],tot;
ll dp[maxn][55];

void add(int u,int v,ll w){
    edge[tot].to=v,edge[tot].next=head[u],edge[tot].w=w;
    head[u]=tot++;
}

void init(){
    mem(head,-1);
    tot=0;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=k;j++)
            dp[i][j]=(j==1||j==0)?0:INF;
}

void dfs(int u,int fa){//树形分组背包
    dp[u][0]=dp[u][1]=0;
    for(int l=head[u];l!=-1;l=edge[l].next){//每个孩子 <=> 每个组
        int v=edge[l].to;
        ll w=edge[l].w;
        if(v==fa) continue;
        dfs(v,u);//1.递归求得每个孩子的最优值
        //2.对每个孩子进行完全背包
        for(int i=k;i>=1;i--){//容量
            for(int j=1;j<=i;j++){//枚举每组的物品
                ll cost=dp[v][j]+(ll)w*j*(k-j)*2;
                dp[u][i]=min(dp[u][i],dp[u][i-j]+cost);
            }
        }
    }
}

int main(){
    //freopen("a.txt","r",stdin);
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&k);
        init();
        for(int i=0;i<n-1;i++){
            int a,b;
            ll c;
            scanf("%d%d%lld",&a,&b,&c);
            add(a,b,c),add(b,a,c);
        }
        dfs(1,0);
        printf("%lld\n",dp[1][k]);
    }
    return 0;
}


bug:分组背包必须先枚举组,再枚举容量,不能先枚举容量!

long long的上限取1ll<<60即可


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值