jzoj1822电子眼【树型DP】

>Description
有N条马路和N个路口,每条马路连接两个路口,每两个路口之间最多只有一条马路。作为一条交通网络,显然每两个路口之间都是可达的。为了更好地管理交通,市长决定在一些路口加装电子眼,用来随时监视路面情况。这些装在路口的电子眼能够监视所有连接到这个路口的马路。现在市长想知道最少需要在多少个路口安装电子眼才能监视所有的马路。市长已经把所有的路口都编上了1~N的号码。

给你地图,你能帮忙吗?


>Input
输入文件第1行包括一个数字N(1<=N<=100000),表示中山市的路口数。接下来N行,每行两个正整数x,y,表示第x个路口和第y个路口有马路相连。

>Output
输出最少需要安装电子眼的数量。


>Sample Input
3
1 2
2 3
1 3

>Sample Output
2

1<=N<=100000


>解题思路
不要相信学校网站

由于有n个点与n条边,所以不管怎样,这张图中都有且仅有一个环,这样就做不了树型DP了,但是可以通过dfs找到环中的一条边(准确地说是找到环中的一个点 c c c,和任意和它相连的一个点 c c cc cc,并且 c c cc cc也在环中),把这条边删掉,就成为一棵树了。

c c c c c cc cc分别作为根做两次DP:
f [ i ] f[i] f[i]表示由 c c c c c cc cc作为根,第 i i i个节点的子树中的边都被监视,的最少电子眼数;
f [ i ] [ 0 ] f[i][0] f[i][0]表示以上要求合理的情况下,第 i i i个节点不安装电子眼
f [ i ] [ 1 ] f[i][1] f[i][1]表示以上要求合理的情况下,第 i i i个节点安装电子眼

状态转移方程:
f [ i ] [ 0 ] = s u m ( f [ s o n ] [ 1 ] ) f[i][0]=sum(f[son][1]) f[i][0]=sum(f[son][1]) 因为第i个节点不安装电子眼,所以它的儿子必须安装才可以使与i相连的边被监视
f [ i ] [ 1 ] = s u m ( m i n ( f [ s o n ] [ 1 ] , f [ s o n ] [ 0 ] ) ) + 1 f[i][1]=sum(min(f[son][1],f[son][0]))+1 f[i][1]=sum(min(f[son][1],f[son][0]))+1 因为第i个节点安装电子眼,所以它的儿子管它安不安装都可以

最后,因为当初选择的c和cc之间的边被删除了,所以如果要使这一条边也被监视的话,要么在c安装电子眼,要么在cc安装电子眼,答案取min值。


>代码

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

using namespace std;

struct road
{
	int to, next;
} a[200005];
int n, x, y, t, c, cc, ans, h[100005], f[100005][2];
bool s[100005];

void find (int last, int dep) //last表示上一个点,dep表示当前点
{
    if (s[dep])
    {
    	c = dep;
		cc = last;
    	return;
    }
    s[dep] = 1;
	for (int i = h[dep]; i; i = a[i].next)
	 find (dep, a[i].to); //dfs
	s[dep] = 0;
}
bool check (int now, int tt)
{
	if (now == c && tt == cc) return 0;
	if (tt == c && now == cc) return 0;
	return 1; //判断是不是以删除的那条边
}
void work (int dep)
{
	s[dep] = 1;
	f[dep][1] = 1;
	for (int i = h[dep]; i; i = a[i].next)
	  if (!s[a[i].to] && check (dep, a[i].to))
	  {
	 	work (a[i].to);
	 	f[dep][1] += min (f[a[i].to][1], f[a[i].to][0]);
	 	f[dep][0] += f[a[i].to][1]; //状态转移方程
	  }
	s[dep] = 0;
}
int main()
{
	scanf ("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf ("%d%d", &x, &y);
		a[++t].to = y; a[t].next = h[x]; h[x] = t;
		a[++t].to = x; a[t].next = h[y]; h[y] = t; //领接表
	}
	find (0, 1); //找到c和cc(找环)
	
	work (c); //先由c为根做一次dp
	ans = f[c][1];
	memset (f, 0, sizeof(f));
	work (cc); //再有cc为根做一次dp
	
	printf ("%d", min (ans, f[cc][1])); //取min
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值