poj4045——树形DP

题目链接:

http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=28138


题目大意:

有n个小区,有n-1条边将这n个小区连接起来。( 3<=n<=50000)

现在要选择一个小区建设一个电站,小区到电站的电缆是有损耗的。

公式为:I*I*R*Di。

Di表示所有小区到该电站的距离之和,求取最小的损耗。


解题思路:

不能进行暴力枚举,时间复杂度是n^2,会超时。

可以使用树形DP。

建树的方法不一定,看大家自己的喜好。

我定义了3个数组:

Count[i]:i节点的子孙节点的总个数。

dp[i]:      i节点的子孙节点到该节点的距离之和。

dis[i]:     非i节点的子孙节点到i节点的距离之和。

那么最后每个点的距离之和就是: dp[i]+dis[i]。

Count[i]数组的求法:

Count[i]+=(Count[j]+1);

j是i的孩子节点,通过从根节点开始的dfs,从后往前递推,可以很快求出该数组。


dp[i]数组的求法:

dp[i]+=(dp[j]+Count[j]+1);

j是i的孩子节点,通过从根节点开始的dfs,从后往前推,可以很快求出该数组。


dis[i]数组的求法:

dis[j]=dis[i]+(dp[i]-dp[j]-1-Count[j])+(N-1-Count[j]);

j是i的孩子节点,别的子树上的节点想要达到j必须先到达j的父亲节点i。


源代码:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<algorithm>
#include<iostream>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long LL;
const double eps=1e-8;
int N,I,R,root;
struct node
{
    int father;
    vector<int> child;
}c[50005];
LL dp[50005];      //以i为根节点的子树当中到i节点的距离之和
int Count[50005];   //i节点的所有子孙节点的个数
void dfs1(int now)  //求dp数组
{
    int i,j,size,child;
    size=c[now].child.size();
    if(size==0) return;
    for(i=0;i<size;i++)
    {
        child=c[now].child[i];
        dfs1(child);
        dp[now]+=(dp[child]+Count[child]+1);
    }
    return;
}
void dfs2(int now)  //求取Count数组
{
    int i,j,k,size,child;
    size=c[now].child.size();
    if(size==0) return;
    for(i=0;i<size;i++)
    {
        child=c[now].child[i];
        dfs2(child);
        Count[now]+=(Count[child]+1);
    }
    return;
}
int cnt;            //最后可选点的个数
int ans[50005];     //用来记录最后需要输出的数组
LL Min;            //用来记录最小值
LL dis[50005];     //别的子树到i点的距离之和。
void dfs(int now)
{
    int i,j,k,size,t,child;
    size=c[now].child.size();
    if(size==0) return;
    for(i=0;i<size;i++)
    {
        child=c[now].child[i];
        //要到child,先到child的父亲节点
        dis[child]=dis[now]+                    //非弄now子树到达now节点
        (dp[now]-dp[child]-Count[child]-1)+     //除了child的子孙
        (N-1-Count[child]);                     //需要到达child的节点的个数
        dfs(child);
    }
    return;
}
int main()
{
	freopen("in.txt","r",stdin);
	int cs,a,b,father,child,i,j;
	scanf("%d",&cs);
	while(cs--)
	{
	    scanf("%d%d%d",&N,&I,&R);
	    memset(c,0,sizeof(c));
	    for(i=1;i<N;i++)
	    {
	        scanf("%d%d",&a,&b);
	        father=a; child=b;
	        if(c[b].father)
	        {
	            father=b;
	            child=a;
	        }
	        c[child].father=father;
	        c[father].child.push_back(child);
	    }
	    root=1;
	    while(c[root].father)
            root=c[root].father;

        //求取Count数组
        memset(Count,0,sizeof(Count));
        dfs2(root);

        //求取dp数组,本子树的距离之和
        memset(dp,0,sizeof(dp));
        dfs1(root);

        //求取dis数组,其他子树的距离之和
        memset(dis,0,sizeof(dis));
        dfs(root);

        //输出最后的结果
        Min=INF;
        cnt=0;
        for(i=1;i<=N;i++)
        {
            if(dp[i]+dis[i]<Min)
            {
                cnt=0;
                ans[cnt++]=i;
                Min=dp[i]+dis[i];
            }
            else if(dp[i]+dis[i]==Min)
            {
                ans[cnt++]=i;
            }
        }
        printf("%I64d\n",I*I*R*Min);
        printf("%d",ans[0]);
        for(i=1;i<cnt;i++)
        {
            printf(" %d",ans[i]);
        }
        printf("\n");
        if(cs)
            printf("\n");
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值