uva1407 树形分组背包

题目大意:一棵有根树,从根节点出发,移动距离不超过num,求最多能经历多少个点?(顶点重复经过仅算一次)

问题可以转化为“经历k个点最少移动多少距离”。

模型同hdu某次bestcoder第四题Cities。具体模型分析详见那题题解。

dp[u][k][0]:经过k个顶点不回到自身的最小移动距离
dp[u][k][1]:经过k个顶点回到自身的最小移动距离


dp[u][k][1]=min(dp[u][k][1],dp[v][j][1]+dp[u][k-j][1]+2*<u,v>.value);
dp[u][k][0]=min(min(dp[u][k][0],dp[v][j][1]+dp[u][k-j][0]+2*w),dp[v][j][0]+dp[u][k-j][1]+<u,v>.value);


对与多组询问,可以先把询问从小到大排序,然后与dp值比较,可以在线性时间内得到结果。


问题:如果起点不定能否也在此复杂度下求解?


拓展1::求包含根节点的顶点个数为k的子树(不一定包含每个节点的所有子节点)中边权和的最小值

dp[u][k]=min{ dp[u][k],dp[v][j]+<u,v>+dp[u][k-j],1<=j<k&&j<=sum[v] }

注:若子树可以不包含根节点,将上式中j<k改为j<=k即可



 #include<stdio.h>
 #include<string.h>
 #include<iostream>
 #define INF 0x3f3f3f3f
 #define mem(a,b) memset(a,b,sizeof(a))
 using namespace std;

const int maxnode=500+10;

int n,indu[maxnode],root;
struct Edge{
    int to,next,value;
}edge[maxnode<<2];
int head[maxnode],tot;
//dp[u][k][0]:经过k个顶点不回到自身的最小移动距离
//dp[u][k][1]:经过k个顶点回到自身的最小移动距离
int dp[maxnode][maxnode][2];
int sum[maxnode];//以该顶点为根的子树节点个数
int q;//询问数量
int cas;//数据个数

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

void buildtree(){//建树
    mem(indu,0),mem(head,-1),tot=0;
    for(int i=0;i<n-1;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        indu[a]++;
        add(a,b,c),add(b,a,c);
    }
}

void Findroot(){//找到树根
    for(int i=0;i<n-1;i++) if(!indu[i]) { root=i; break; }
}

void dfs(int u,int fa){//求解dp[u][k][0]和dp[u][k][1] O(n^3)
    sum[u]=1;
    dp[u][1][0]=dp[u][1][1]=0;
    for(int i=head[u];i!=-1;i=edge[i].next){
        int v=edge[i].to,w=edge[i].value;
        if(v==fa) continue;
        dfs(v,u);
        sum[u]+=sum[v];
        for(int k=sum[u];k>=1;k--){//<strong>分组背包中容量(总节点个数)必须倒序以保证每个组(每个子节点为跟的树中的节点)中仅取出一个元素</strong>
            for(int j=1;j<=k&&j<=sum[v];j++){
                dp[u][k][1]=min(dp[u][k][1],dp[v][j][1]+dp[u][k-j][1]+2*w);
                dp[u][k][0]=min(dp[u][k][0],dp[v][j][1]+dp[u][k-j][0]+2*w);
                dp[u][k][0]=min(dp[u][k][0],dp[v][j][0]+dp[u][k-j][1]+w);
            }
        }
    }
}

void solve(){//法一:O(q*n)
    scanf("%d",&q);
    printf("Case %d:\n",++cas);
    while(q--){
        int num; scanf("%d",&num);
        for(int i=n;i>=1;i--)
            if(dp[root][i][1]<=num||dp[root][i][0]<=num) { printf("%d\n",i); break; }
    }
}
/*void solve(){//法二:O(q*log(q)+n)
    scanf("%d",&q);
    for(int i=1;i<=q;i++){
        query[i].id=i;
        scanf("%d",&query[i].d);
    }
    sort(query+1,query+q+1,cmp);
    int cnt=1;
    for(int i=1;i<=n&&cnt<=q;i++){
        int tmp=min(dp[root][i][1],dp[root][i][0]);
        while(query[cnt].d<tmp){
            query[cnt++].num=i-1;
        }
    }
    for(;cnt<=q;cnt++) query[cnt].num=n;
    sort(query+1,query+q+1,cmp2);
    printf("Case %d:\n",++cas);
    for(int i=1;i<=q;i++) printf("%d\n",query[i].num);
}*/
int main(){
    //freopen("a.txt","r",stdin);
    while(scanf("%d",&n)!=EOF,n){
        buildtree();
        Findroot();
        mem(dp,INF);
        dfs(root,-1);
        solve();
    }
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值