UVa 10859 Placing Lampposts (树形DP)

本文针对UVA10859问题提供了解决方案,采用树形动态规划的方法,在一棵或多棵无向无环图中放置最少数量的灯以照亮所有边,并使尽可能多的边被两盏灯照亮。

UVA 10859 Placing Lampposts

题目大意:

给一个n点m边的无向无环图,在尽量少的节点上放灯,使得所有边都被照亮,一个点上的灯可以照亮与该点相连的所有边,在灯最少的前提下,使得被两盏灯照亮的边尽可能多.输出所需灯个数,被两盏灯照亮的边个数,被一盏灯照亮的边个数.

题目分析:

无向无环图,显然就是一棵无根树.而m有可能小于n-1,那么就可能是多棵树,即森林.
显然这是树形DP,但是需要维护的值有两个最少灯个数和最多边个数.其实边只有两种:被一盏灯照亮和被两盏灯照亮.所以要使被两盏灯照亮的边尽可能多,就是要使被一盏灯照亮的边尽可能少.就变成了求两个最小值.
对于两个最小值的求解,白书上面给出了一种巧妙的办法,把两者和在一起.

x=Ma+b

其中,M是一个很大的正整数,M应当大于b的理论最大值与a的理论最小值之差.因为在a不同的情况下,b是不能影响dp值结果的,所以a的单位1M需要大于b的最大值.这样的好处在于如此比较dp值先比较a再比较b,比较简便(但是需要注意M不要取太大,避免溢出).

既然是DP,首先考虑其状态的定义,d(u,p,k)表示以u结点为根的子树全部照亮所需的最小dp值,其中p表示u的父节点,k表示父节点的状态:是否有灯.

代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int M=2000;//分割两个量的常量 
const int maxn=1000+10;

int fir[maxn],nxt[maxn<<1],to[maxn<<1],ecnt;

void add_edge(int u,int v)
{
    nxt[++ecnt]=fir[u];fir[u]=ecnt;to[ecnt]=v;
    nxt[++ecnt]=fir[v];fir[v]=ecnt;to[ecnt]=u;
}

int d[maxn][2],vis[maxn][2];

int dp(int u,int p,int k)//dp值由两部分组成,dp/M表示灯的个数,dp%M表示仅有一盏灯照亮的道路数 
//dp(u,p,k)表示当前以u节点为根的子树所需要的最小dp值,其中p表示u父节点,p=-1时,u为根,k表示父节点有没有灯 0无1有 
{
    if(vis[u][k]) return d[u][k];//记忆化 
    vis[u][k]=1;
    int& ans=d[u][k];ans=M;//无论p是否有灯,u均可以有灯 
    for(int i=fir[u];i;i=nxt[i]) if(to[i]!=p) ans+=dp(to[i],u,1);
    if(p!=-1&&!k) ++ans;//若该点不是根并且p没灯,那么p与u之间的道路仅有一盏灯照亮 
    if(p==-1||k) {//若u为根或者p有灯,那么u可以没灯 
        int tot=0;
        for(int i=fir[u];i;i=nxt[i]) if(to[i]!=p) tot+=dp(to[i],u,0);
        if(p!=-1) ++tot;//若u不为根,那么p与u之间的道路仅有一盏灯照亮 
        ans=min(ans,tot);
    }
    return ans;
}

void init()//初始化 
{
    ecnt=0;
    memset(fir,0,sizeof(fir));
    memset(vis,0,sizeof(vis));
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--) {
        init();//记得每次初始化 
        int n,m;
        scanf("%d%d",&n,&m);
        for(int u,v,i=0;i<m;i++) {
            scanf("%d%d",&u,&v);
            add_edge(u,v);
        }
        int ans=0;
        for(int i=0;i<n;i++) if(!vis[i][0]) ans+=dp(i,-1,0);//一旦存在未访问过的点,做一次树形dp 
        printf("%d %d %d\n",ans/M,m-ans%M,ans%M);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值