UVALive-3683 A Scheduling Problem(treedp)

本文探讨了一个涉及任务调度的问题,通过使用动态规划的方法来优化任务的完成时间。文章介绍了一种贪心策略,并通过实例说明了该策略存在的问题,最终提出了正确的DP算法实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意: 有n(n<=200) 个恰好需要一天完成的任务,要求用最少的时间完成所有任务。任务可以并行完成,但必须满足一些约束,约束分为有向约束和无向约束两种,其中A->B表示A必须在B之前完成,A-B表示A和B不能在同一天晚上。输入保证约束图是将一颗树的一些边定向之后得到的。


分析: 

    这题好神,我一开始根据题目的提示想了个错误的贪心,然后疯狂WA了一天。

    我一开始错误的贪心是认为对于原图最长链k大于2的情况,答案为k+1当且仅当只有一个无向边定向后参与入了新的最长链的构造,于是我一开始的做法就是特判k = 2的情况后枚举所有的单个无向边根据它两边连入连出的最长有向链长度进行贪心判断,WA了一天后我发现随机生成的一个数据卡掉了我的贪心:


    可以看出这样中间两条无向边无论怎么定向答案都会大于原图的有向最长链3,而且这是两条无向边联合影响下产生的,我之前单独考虑枚举一条无向边而忽视其他无向边的影响的做法一定是错误的。

    正解是dp,首先根据题意我们可以把问题抽象出来:给定一棵树,一些边已经定了方向,现在让你把其他边也定好方向,使得树上的最长有向链的最大值最小,然后题目提示你结果为k或k加一(k为原图最长链),随便找一个点作为根节点后(比如第一个点),我们可以把树上的所有有向链分为经过当前根节点和不经过当前根节点两种,不经过当前根节点的一定是在根节点的儿子的子树中,这种情况可以递归处理,然后我们每次只需要处理经过当前根节点的链就行了。

    因为我们知道答案最多为k+1,所以我们可以抽象出来这样一种状态,fin[i]表示在以i为根的子树中满足条件的所有方案中指向i的有向边最短的方案中的有向边长度,fout同理,于是我们就可以设计出状态转移了:若要求fin[i]最小那么我们可以枚举这个最小值,然后对于小于这个最小值的儿子我们就可以贪心的全部将其连向i,具体做法就是把i连出的无向儿子按照fin[son]进行排序然后再枚举。fout[i]做法同理。

  

#include<iostream>
#include<string>
#include<algorithm>
#include<cstdlib>
#include<cstdio>
#include<set>
#include<map>
#include<vector>
#include<cstring>
#include<stack>
#include<queue>
#define INF 0x3f3f3f3f
#define eps 1e-9
#define N 210
using namespace std;
typedef pair<int,int> pii;
int root,n,ans,vis[N],cd[N],fin[N],fout[N];
pii t[N];
vector<pii> G[N];
bool camp1(pii a,pii b)
{
	return a.first < b.first;
}
bool camp2(pii a,pii b)
{
	return a.second < b.second;
}
bool got(int u)
{
	char t[20];
	scanf("%s",t);
	int l = strlen(t);
	if(t[0] == '0') return false;
	if(t[l-1] == 'd' || t[l-1] == 'u')
	{
		if(t[l-1] == 'd') 
		{
			t[l-1] = '\0';
			int v = atoi(t);
			G[v].push_back(make_pair(u,1));
			G[u].push_back(make_pair(v,2));
 		}
		else 
		{
			t[l-1] = '\0';
			int v = atoi(t);
			G[u].push_back(make_pair(v,1));
			G[v].push_back(make_pair(u,2));
		}
	}
	else 
	{
		G[u].push_back(make_pair(atoi(t),0));
		G[atoi(t)].push_back(make_pair(u,0));
	}
	n = max(n,atoi(t));
	return true;
}
void dfs(int u)
{
	vis[u] = true;
	for(int i = 0;i < G[u].size();i++)
	{
		int v = G[u][i].first,op = G[u][i].second;
		if(op != 1) continue;
		if(!vis[v]) dfs(v);
		cd[u] = max(cd[u],cd[v] + 1);
	}
	ans = max(ans,cd[u]);
}
void dfs_dp(int u,int fa)
{
	int max_in = 0,max_out = 0;
	for(int i = 0;i < G[u].size();i++)
	{
		int v = G[u][i].first,op = G[u][i].second;
		if(v == fa) continue;
		dfs_dp(v,u);
		if(op == 1) max_out = max(max_out,fout[v]);
		if(op == 2) max_in = max(max_in,fin[v]);
	}
	int cnt = 0;
	for(int i = 0;i < G[u].size();i++)
	{
		int v = G[u][i].first,op = G[u][i].second;
		if(v == fa || op) continue;
		t[++cnt] = make_pair(fout[v],fin[v]);
	}
	if(!cnt)
	{
		if(max_out + max_in + 1 <= ans)
		{
			fout[u] = max_out + 1;
			fin[u] = max_in + 1; 
		}
		else fout[u] = fin[u] = INF;
		return;
	}
	else fout[u] = fin[u] = INF;
	t[++cnt]= make_pair(0,0);
	sort(t+1,t+1+cnt,camp1);
	for(int i = 1;i <= cnt;i++)
	{
		int temp = 0;
		for(int j = i+1;j <= cnt;j++) temp = max(temp,t[j].second);
		if(max(temp,max_in) + max(t[i].first,max_out) + 1 <= ans) 
		{
			fout[u] = 1 + max(max_out,t[i].first);
			break;
		}
	}
	sort(t+1,t+1+cnt,camp2);
	int temp = 0;
	for(int i = cnt;i;i--)
	{
		if(max(temp,max_out) + max(t[i].second,max_in) + 1 <= ans) 
		 fin[u] = 1 + max(t[i].second,max_in);
		temp = max(temp,t[i].first);
	}
	sort(t+1,t+1+cnt,camp2);
	temp = 0;
	for(int i = cnt;i;i--)
	{
		if(max(temp,max_out) + max(t[i].second,max_in) + 1 <= ans) 
		 fout[u] = 1 + max(t[i].first,max_out);
		temp = max(temp,t[i].second);
	}
}
bool check()
{
	dfs_dp(1,-1);
	if(fin[1] > ans) return true;
	return false;
}
int main()
{
	while(~scanf("%d",&root) && root)
	{
		ans = 0;
		n = root;
		memset(vis,0,sizeof(vis));
		while(got(root));
		while(~scanf("%d",&root) && root)
		{
		    n = max(n,root);
			while(got(root));
		}
		for(int i = 1;i <= n;i++) cd[i] = 1;
		for(int i = 1;i <= n;i++) if(!vis[i]) dfs(i);
		printf("%d\n",ans + check());
		for(int i = 1;i <= n;i++) G[i].clear();                                                                                    
	}
}     

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值