放置街灯(UVA 10859)

问题描述

给你一个n个点m条边的无向无环图,在尽量少的节点上放灯,使得所有灯都被照亮。每盏灯将照亮以它为一个端点的所有边。在灯的总数最小的前提下,被两盏灯同时照亮的边数应尽量大。

输入格式

输入的第一行为测试数据组数T(T≤30)。每组数据第一行为两个整数n和m(m<n≤1000),即点数(所有点编号为0~n-1)和边数;以下m行每行为两个不同的整数a和b,表示有一条边连接a和b(0≤a,b≤n)。

输出格式

对于每组数据,输出3个整数,即灯的总数,被2个灯照亮的边数和只被一个灯照亮的边数。

分析

因为是无向无环图(即森林),所以这道题是树上的动态规划。首先,要先知道一点:对于2个变量a,b,如果要在a最小的前提下,求b的最小值,可以求x=M*a+b的最小值,那么最后答案即为min(a)=x/M,min(b)=x%M,其中M值要大于a的理论最大值与b的理论最小值之差。道理很简单,当M足够大的时候,a决定整个式子的值,而b的值对式子的值不会造成多大的影响,只有当a值不变时,b才能对式子的值造成影响。

那么,对于这道题来说,有2个待优化的条件:1、灯的总数a最小;2、在1的前提下,被2个灯照亮的边数b最大。一个最大,一个最小,显然不方便优化,所以在上面结论的提示下,考虑将第二个条件等价为被1个灯照亮的边数c最小(一条边只能同时被一盏或者2盏灯照亮),这时候就可以直接套用上面的结论了。设x=M*a+c,这里M可以取2000,或者更大,但是要注意不能太大,否则在运算时可能会造成数据溢出。

分析到这里,动态规划的状态方程也不难想了,因为对于每个节点i来说,只有2种方案,放灯或者不放灯,但是节点i放灯与否与它的父节点也有关系,所以设d(i,j)为节点i的父节点j是否放灯(j值为1是放灯,0是不放)的最小x值。下面分别对2种方案进行分析:

1、节点i不放灯。那么i的父节点必须放灯(j=1)或者i本身是根节点。此时d(i,j)=sum{d(k,0)|k取遍i的所有子节点},如果i不是根,还要加上1,因为节点i与其父节点这条边上只有1盏灯

2、节点i放灯。此时d(i,j)=sum{d(k,1)|k取遍i的所有子节点}+M,同样的,如果j=0,且i不是根,还得加上1,因为节点i与其父节点这条边上只有1盏灯。

  

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

vector<int> adj[1010];
int vis[1010][2], d[1010][2], n, m;
int dp(int i, int j, int f) {
	if (vis[i][j])
		return d[i][j];
	vis[i][j] = 1;
	int& ans = d[i][j];

	ans = 2000;
	for (int k = 0; k < adj[i].size(); k++) {
		if (adj[i][k] != f) {
			ans += dp(adj[i][k], 1, i);
		}
	}
	if (!j&&f >= 0)
		ans++;

	if (j || f < 0) {
		int sum = 0;
		for (int k = 0; k < adj[i].size(); k++)
			if (adj[i][k] != f)
				sum += dp(adj[i][k], 0, i);
		if (f >= 0)
			sum++;
		ans = min(ans, sum);
	}
	return ans;
}

int main() {
	int T, a, b;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &m);
		for (int i = 0; i < n; i++) {
			adj[i].clear();
		}
		for (int i = 0; i < m; i++) {
			scanf("%d%d", &a, &b);
			adj[a].push_back(b);
			adj[b].push_back(a);
		}
		memset(vis, 0, sizeof(vis));
		int ans = 0;
		for (int i = 0; i < n; i++) {
			if (!vis[i][0])
				ans += dp(i, 0, -1);
		}
		printf("%d %d %d\n", ans / 2000, (m - ans) % 2000, ans % 2000);
	}
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值