Uva1380一个调度问题

题意:

给定一颗树 有些边已经标好方向 现在要给其余的边标上方向 使得最长的有向链最短
HIT: 题目额外给了一个结论 假设已经确定方向的边所能得到的最长链为k 最后的最长链一定k 或k+1

思路:
1. O(n^2logn).

详解:https://www.cnblogs.com/KonjakJuruo/p/5969831.html
利用题目给的结论 我们实际上只需要解决以下这个几乎等价的问题
判断所给的树是否可以通过改变无向边的方向构造出最长链不超过k的方案
如果是的 那么最长链就是k否则为k+1
对于每颗子树的最长链 它要么经过这颗子树的根节点 要么在这个根节点的某个儿子所对应的子树中
因此只需递归地去检验每颗子树是否合法即可。

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

const int N = 200+5;
const int INF = 1000000;

struct Edge {
  int u, v, d; // d=1 means u->v, d=2 means v->u, d=0 means u-v
  Edge(int u=0, int v=0, int d=0):u(u),v(v),d(d){}
};

vector<Edge> edges[N];
int n, root, longest, have_father[N], f[N], g[N];

bool input(){
	bool have_data = false;
	int a, b;
	char ch;
	n = 0;
	memset(have_father,0,sizeof(have_father));
	for(int i = 0; i < N; i++) edges[i].clear();
	while(scanf("%d",&a) == 1 && a){
		//printf("a = %d ", a);
		have_data = true;
		if(a > n) n = a;
		while(scanf("%d%c",&b,&ch) == 2&&b){
			//printf("%d%c ",b,ch);
			if(b > n) n = b;
			have_father[b] = 1;
			if(ch == 'd'){
				edges[a].push_back(Edge(a, b, 1));
				edges[b].push_back(Edge(b, a, 2));
			}
			else if(ch == 'u'){
				edges[a].push_back(Edge(a, b, 2));
				edges[b].push_back(Edge(b, a, 1));
			}
			else{
				edges[a].push_back(Edge(a, b, 0));
			}
		}
	}
	// 找根节点 
	if(have_data){
		for(int i = 1; i <= n; ++i) if(!have_father[i]&&!edges[i].empty()) {
			root = i; break;
		}
	}
	return have_data;
}
// 求出 u 为根的子树的最长有向链的长度 
int dfs(int u){
	int ans = 0;
	for(int i = 0; i < edges[u].size(); ++i){
		int v = edges[u][i].v;
		if(edges[u][i].d == 1) // u -> v
			ans = max(ans, dfs(v)+1);
	}
	return ans;
}
// 孩子中的无向边 
struct UndirectedSon {
  int w, f, g;
  UndirectedSon(int w=0, int f=0, int g=0):w(w),f(f),g(g){}
};

bool cmp_f(const UndirectedSon& w1, const UndirectedSon& w2) {
  return w1.f < w2.f;
}

bool cmp_g(const UndirectedSon& w1, const UndirectedSon& w2) {
  return w1.g < w2.g;
}

// 检查 u 为根的子树 
bool dp(int u, int fa){
	if(edges[u].empty()){
		f[u] = g[u] = 0; 
		return true;
	}
	vector<UndirectedSon> sons;
	int f0 = 0, g0 = 0;
	
	for(int i = 0; i < edges[u].size(); ++i){
		int w = edges[u][i].v;
		if(w == fa) continue;
		//dp(w, u);
		if(!dp(w, u)) return false;
		int d = edges[u][i].d;
		if(d == 0){ // 把孩子中的无向边放在一个数组 
			sons.push_back(UndirectedSon(w, f[w], g[w]));
		}
		else if(d == 1) g0 = max(g0, g[w]+1);
		else f0 = max(f0, f[w] + 1);
	}
	// 1. 孩子全是有向边,这个情况最简单 
	if(sons.empty()){
		f[u] = f0; g[u] = g0;
		if(f0 + g0 > longest) { f[u] = g[u] = INF; }
		return f[u] < INF;
	}
	
	f[u] = g[u] = INF;
	// 2. 孩子中存在无向边,需要规划它的方向,算出f[u] 和 g[u] 
	int s = sons.size();
	//printf("size = %d\n", s);
	sort(sons.begin(), sons.end(), cmp_f);
	int maxg[N]; // maxg[i] is max{sons[i].g, sons[i+1].g, ...} 
	maxg[s-1] = sons[s-1].g;
	for(int i = s-2; i >= 0; --i) maxg[i] = max(sons[i].g, maxg[i+1]);
	for(int i = 0; i <= s; ++i){
		int f1 = f0, g1 = g0;
		if(i > 0) f1 = max(f1, sons[i-1].f+1);
		if(i < s) g1 = max(g1, maxg[i]+1);
		if(f1 + g1 <= longest) { f[u] = min(f[u], f1); break;	}
	}
	
	sort(sons.begin(), sons.end(), cmp_g);
	int maxf[N]; // maxf[i] is max{sons[i].f, sons[i+1].f, ...}
	maxf[s-1] = sons[s-1].f;
	for(int i = s-2; i >= 0; --i) maxf[i] = max(sons[i].f, maxf[i+1]);
	for(int i = 0; i <= s; ++i){
		int f1 = f0, g1 = g0;
		if(i > 0) g1 = max(g1, sons[i-1].g+1);
		if(i < s) f1 = max(f1, maxf[i]+1);
		if(f1 + g1 <= longest) { g[u] = min(g[u], g1); break;	}
	}
	
	return f[u] < INF;
}

int main()
{
	freopen("in.txt","r",stdin);
	while(input()){
		longest = 0;
		for(int i = 1; i <= n; ++i) longest = max(longest, dfs(i));
		if(dp(root, -1)) printf("%d\n", longest+1);
		else printf("%d\n",longest+2);
	}

	return 0;
}


2.O(n^3)

我们可以建立三个数组 f[x][y] up[x] down[x]
f[x][y]代表:从该根节点x出发的向下(u->…)的最长链最小值为y时,向上(…->x)的最长链最小值为多少
up[x] down[x]分别代表该根节点向上/下的最长链的最小值。对应于上面的f[], g[]
对于当前子树的根节点u,它与孩子节点v的边有3种情况:

  1. u -> v。一个确定的向下有向边,此时可以知道u的最长向下链长最少也要 >= down[v]+1,所以 <= down[v]的最长向下链长是没有意义的,它不应该存在解。
  2. u <- v。一个确定的向上有向边,此时可以知道u的最长向上链长最少也要 >= up[v]+1,所以更新所有f[u][i], i 是所有向下链长的可能取值,即0-ans
  3. u – v。无向边,v为根的子树一定是满足条件的(因为我们是递归检查,先检查小的子树,一个不满足直接返回false),对于v,up[v] + down[v] <= ans 成立,对于他的父节点u, f[u][k](k <= down[v]),的值最小为up[v]+1,它不一定满足条件,但已经是尽可能小了,后面会检查它的合法性.
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 200+5;
const int INF = 10000;
struct Edge {
  int u, v, d; // d=1 means u->v, d=2 means v->u, d=0 means u-v
  Edge(int u=0, int v=0, int d=0):u(u),v(v),d(d){}
};

vector<Edge> edges[N];
int n, root, longest, have_father[N], up[N], down[N], f[N][N];

// 求出 u 为根的子树的最长有向链的长度 
int dfs(int u){
	int ans = 0;
	for(int i = 0; i < edges[u].size(); ++i){
		int v = edges[u][i].v;
		if(edges[u][i].d == 1) // u -> v
			ans = max(ans, dfs(v)+1);
	}
	return ans;
}

bool input(){
	bool have_data = false;
	int a, b;
	char ch;
	n = 0;
	memset(have_father,0,sizeof(have_father));
	for(int i = 0; i < N; i++) edges[i].clear();
	
	while(scanf("%d",&a) == 1 && a){
		//printf("a = %d ", a);
		have_data = true;
		if(a > n) n = a;
		while(scanf("%d%c",&b,&ch) == 2&&b){
			//printf("%d%c ",b,ch);
			if(b > n) n = b;
			have_father[b] = 1;
			if(ch == 'd'){
				edges[a].push_back(Edge(a, b, 1));
				edges[b].push_back(Edge(b, a, 2));
			}
			else if(ch == 'u'){
				edges[a].push_back(Edge(a, b, 2));
				edges[b].push_back(Edge(b, a, 1));
			}
			else{
				edges[a].push_back(Edge(a, b, 0));
			}
		}
	}
	// 找根节点 
	if(have_data){
		for(int i = 1; i <= n; ++i) if(!have_father[i]&&!edges[i].empty()) {
			root = i; break;
		}
	}
	return have_data;
}

bool dfs(int u, int fa){
	for(int i = 0; i < edges[u].size(); ++i){
		int v = edges[u][i].v;
		if(v == fa) continue;
		if(!dfs(v, u)) return false;
		int d = edges[u][i].d;
		// 有向边 u -> v
		if(d == 1){
			for(int i = 0; i <= down[v]; ++i) f[u][i] = INF;
		}
		// 有向边 v -> u 
		else if(d == 2){
			for(int i = 0; i <= longest; ++i) f[u][i] = max(f[u][i], up[v]+1);
		}
		// 无向边  u -- v
		else{
			for(int i = 0; i <= down[v]; ++i) f[u][i] = max(f[u][i], up[v]+1);
		} 
	}
	bool ok = 0;
	for(int i = 0; i <= longest; ++i){
		if(f[u][i] + i <= longest){
			ok = 1;
			up[u] = min(up[u], f[u][i]);
			down[u] = min(down[u], i);
		}
	}
	return ok;
}

int main()
{
	freopen("in.txt","r",stdin);
	while(input()){
		memset(up, 0x3f, sizeof(up));
		memset(down, 0x3f, sizeof(down));
		memset(f, 0, sizeof(f));
		longest = 0;
		for(int i = 1; i <= n; ++i) longest = max(longest, dfs(i));
		if(dfs(root, -1)) printf("%d\n",longest+1);
		else printf("%d\n",longest+2);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值