UVA 1218 Perfect Service (树上 DP)

UVA 1218 Perfect Service (树上 DP)

本题的状态数较多,一个节点有三种状态表示(一开始我只想到两个):
0、该节点不是服务器,该节点的父节点也不是服务器
1、该节点是服务器。
2、该节点不是服务器,该节点的父节点是服务器
对于着三种状态,分别用d(u,0),d(u,1),d(u,2)表示

对于以上三种状态进行分析,并写出状态转移方程
1、对于第一种状态,则子节点里有且仅有一个为服务器。需要枚举每一个子节点为d(v,1),把其他子节点的d(v,0)相加。如果在每次枚举时进行计算,那么该操作时间复杂度为O(k2)。可以通过一个sum累计所有子节点的d(v,0),在进行枚举时,只需在sum的基础上加上一个d(v,1)再减去一个d(v,0)即可。时间复杂度降为O(k)
状态转移方程为:d(u,0)=min(d(u,0),sum+d(v,1)-d(v,0))
2、对于第二种状态,则该节点的子节点可以时服务器也可以不是服务器。
状态转移方程为:d(u,1)= ∑ m i n ( d ( v , 1 ) , d ( v , 2 ) ) \displaystyle\sum_{}^{} min(d(v,1),d(v,2)) min(d(v,1),d(v,2))
3、对于第三种状态,所有子节点都必须不是服务器
状态转移方程为d(u,2)= ∑ d ( v , 0 ) \displaystyle\sum_{}^{} d(v,0) d(v,0),即为前面的sum。

初始化的值为
d(u,0)=INF ,INF最好设为比max_n多一点,以防在相加时溢出。
d(u,1)=1
d(u,2)=0

具体实现可以先将无根树转换为以1为根节点的有根树,在进行dp(但会很慢)
也可以直接用dfs从1节点遍历树,用vis数组标记访问过的节点,这样就避免了重复访问(即在子节点和父节点间无限循环),记得在对子节点进行完dfs后,将该子节点标记为未访问过,因为还要计算d(u,0)。如果不将子节点恢复为未访问,那么将无法计算。

代码如下:

#include<bits/stdc++.h>
using namespace std;
const int max_n=1e4+5;
int dp[max_n][3];//dp(u,0)表示该节点和父节点不是服务器,dp(u,1)表示该节点是服务器,dp(u,2)表示该节点不是服务器但父节点是服务器 
int n;
vector<int> son[max_n];
bool vis[max_n];
void dp_(int root)
{
	vis[root]=1;
	dp[root][0]=max_n;
	dp[root][1]=1;
	dp[root][2]=0;
	queue<int> q;
	for(int i=0;i<son[root].size();i++)
	if(!vis[son[root][i]])
	{
		vis[son[root][i]]=1;
		q.push(son[root][i]);
		dp_(son[root][i]);
		vis[son[root][i]]=0;
		dp[root][2]+=dp[son[root][i]][0];
		dp[root][1]+=min(dp[son[root][i]][2],dp[son[root][i]][1]);
	}
	for(int i=0;i<son[root].size();i++)
	if(!vis[son[root][i]])
		dp[root][0]=min(dp[root][0],dp[root][2]+dp[son[root][i]][1]-dp[son[root][i]][0]);
}
void solve(void)
{
	memset(vis,false,sizeof(vis));

	dp_(1);
	printf("%d\n",min(dp[1][0],dp[1][1]));
}
int main(void)
{
//	freopen("out.txt","w",stdout);
	while(true)
	{
		scanf("%d",&n);
		for(int i=1;i<=n-1;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			son[a].push_back(b);
			son[b].push_back(a);
		}
		int t;
		scanf("%d",&t);
		solve();
		if(t==-1)break;
		for(int i=1;i<=n;i++)
		son[i].clear();
	}
//	fclose(stdout);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值