HDU 3572 Task Schedule(最大流问题,sap算法)

/*
题意:用m个机器,处理n个任务,每个任务必须在[si,ei]时间段完成,需要pi天才能完成。每个机器只能处理一个任务,
即每天只能处理m个任务。
题解:可以采用贪心法处理,区间覆盖问题,可以参见刘汝佳的书。
或者采用最大流,建图:把每个任务和每一天看做一个点,增加源点s和汇点t,在s和每个任务之间连一条边,容量为持续
天数;在每一天和t之间连一条边,容量为m;在每个任务和对应天数之间连一条边,容量为1。然后最大流量即为所求。
实现:sap算法,算法复杂度比Dinic更低。
sap详细介绍参见:http://chuanwang66.iteye.com/blog/1450715
其中涉及到三种优化,这一部分讲的特别好。
主要思路为:首先建立邻接表,然后建立层次图,查找最短最广路径。
*/


//sap算法递归实现

#include <iostream>
using namespace std;
const int nMax = 2000;
const int mMax = 500000;
const int INF = 0x7fffffff;
struct Edge
{
	int v, w, next;
	Edge(){}
	Edge(int v, int w, int next):v(v), w(w), next(next){}
}adj[mMax];
int ind;
int head[nMax];//顶点对应的第一条弧
int dis[nMax];//顶点所在层次数
int vn[nMax];//各个层次顶点个数
int t, n, m;
int pi, si, ei;
int source, sink, NV;//源点、汇点、总顶点数

void addEdge(int u, int v, int w)
{
	adj[ind] = Edge(v, w, head[u]);
	head[u] = ind ++;
	adj[ind] = Edge(u, 0, head[v]);
	head[v] = ind ++;
}

int dfs(int cur, int cost)
{
	if(cur == sink)
		return cost;
	int leave = cost;//leave表示剩余可利用流量
	int MinDis = NV;
	for(int id = head[cur]; id != -1; id = adj[id].next)
	{
		int v = adj[id].v;
		if(adj[id].w)
		{
			if(dis[v] + 1 == dis[cur])
			{
				int MinFlow = min(leave, adj[id].w);//原来这里出错,需要比较的是leave和adj[id].w的值,因为leave的值在不断变化
				MinFlow = dfs(v, MinFlow);//找到从v出发到sink的最广路径的最小流量
				adj[id].w -= MinFlow;
				adj[id ^ 1].w += MinFlow;
				leave -= MinFlow;
				if(dis[source] >= NV) return cost - leave;//总流量 - 剩余流量
				if(leave == 0) break;
			}
			if(dis[v] < MinDis)//MinDis存储与cur相连顶点中最小层次数,便于没有找到可行弧时候的更新。
				MinDis = dis[v];
		}
	}
	if(leave == cost)//总流量等于剩余流量,则没有查找到可行弧
	{
		if((-- vn[dis[cur]]) == 0) dis[source] = NV;
		dis[cur] = MinDis + 1;
		vn[dis[cur]] ++;
	}
	return cost - leave;
}

int sap(int s, int e)
{
	source = s;
	sink = e;
	NV = e + 1;
	memset(dis, 0, sizeof(dis));
	memset(vn, 0, sizeof(vn));
	vn[0] = NV;
	int ans = 0;
	while(dis[source] < NV)//使用dis[source] >= NV作为终止条件
	{
		ans += dfs(s,INF);
	}
	return ans;
}

int main()
{
	//freopen("e://data.in", "r", stdin);
	int s, e;
	scanf("%d", &t);
	for(int cas = 1; cas <= t; cas ++)
	{
		int Max = 0;
		int sum = 0;
		s = 0;
		ind = 0;
		memset(head, -1, sizeof(head));
		scanf("%d %d", &n, &m);
		for(int i = 1; i <= n; ++ i)
		{
			scanf("%d %d %d", &pi, &si, &ei);
			sum += pi;
			addEdge(s, i, pi);
			Max = max(Max, ei);
			for(int j = si; j <= ei; ++ j)
				addEdge(i, n + j, 1);
		}
		e = n + Max + 1;
		for(int i = 1; i <= Max; ++ i)
			addEdge(n + i, e, m);
		int a;
		if((a = sap(s, e)) == sum)
			printf("Case %d: Yes\n\n", cas);
		else
			printf("Case %d: No\n\n", cas);
	}
	return 0;
}


//非递归实现

#include <iostream>
//#define TEST
using namespace std;

const int nMax = 2000;
const int mMax = 500000;
const int INF = 0x7fffffff;
struct Edge
{
	int u, v, w;
	int next;
	Edge(){}
	Edge(int u, int v, int w, int next):u(u), v(v), w(w), next(next){}
}adj[mMax];
int ind;
int dis[nMax];//距离
int pre[nMax];//路径
int head[nMax];//第一条弧
int flow[nMax];//允许弧中最小流
int vn[nMax];//每层对应顶点个数
int cur[nMax];//当前弧
int t, n, m;
int pi, si, ei;

void addEdge(int u, int v, int w)//相邻接表中增加边
{
	adj[ind] = Edge(u, v, w, head[u]);
	head[u] = ind ++;
	adj[ind] = Edge(v, u, 0, head[v]);
	head[v] = ind ++;
}

int sap(int s, int e, int n)//sap算法,最短增广路经
{
	int u = s;
	int MaxFlow = 0;
	memset(dis, 0, sizeof(dis));
	flow[s] = INF;
	memset(vn, 0, sizeof(vn));
	pre[s] = -1;
	for(int i = 0; i < n; ++ i)
		cur[i] = head[i];//每一个顶点对应的当前弧
	while(d[s] < n)//其实这里不需要什么d[s] < n,因为终止条件只需要层次图出现中断即可,所以while(1)同样正确
	{
		bool flag = false;
		if(u == e)
		{
			MaxFlow += flow[e];
			for(int v = pre[e]; v != -1; v = pre[v])
			{
				int id = cur[v];
				adj[id].w -= flow[e];//对增广路经进行处理
				adj[id ^ 1].w += flow[e];
				if(adj[id].w == 0) u = v;//退回到权值为0的弧首,因为如果等于0,后面的节点肯定无法到达的
			}
		}
		for(int id  = cur[u]; id != -1; id = adj[id].next)//寻找可行弧
		{
			int v = adj[id].v;
			if(adj[id].w > 0 && dis[v] + 1 == dis[u])
			{
				flag = true;
				pre[v] = u;
				flow[v] = min(flow[u], adj[id].w);
				cur[u] = id;
				u = v;
				break;
			}
		}
		if(!flag)//找不到可行弧,所以需要变更dis[u]的值
		{
			if((-- vn[dis[u]]) == 0) break;//层次图中断
			int MinDis = n;//寻找相连边中,最小dis[v]值
			cur[u] = head[u];
			for(int id = head[u]; id != -1; id = adj[id].next)
			{
				int v = adj[id].v;
				if(adj[id].w > 0 && dis[v] < MinDis)//edge[i].w > 0这个条件需要注意,代表还可达
				{
					MinDis = dis[v];
					cur[u] = id;
				}
			}
			dis[u] = MinDis + 1;//对dis[u]进行重新定义
			vn[dis[u]] ++;
			if(u != s) u = pre[u];//回溯
		}
	}
	return MaxFlow;
}

int main()
{
	//freopen("e://data.in", "r", stdin);
	//freopen("e://data2.out", "w", stdout);
	int s, e;
	scanf("%d", &t);
	for(int cas = 1; cas <= t; cas ++)
	{
		int Max = 0;
		int sum = 0;
		s = 0;
		ind = 0;
		memset(head, -1, sizeof(head));
		scanf("%d %d", &n, &m);
		for(int i = 1; i <= n; ++ i)
		{
			scanf("%d %d %d", &pi, &si, &ei);
			sum += pi;
			addEdge(0, i, pi);
			Max = max(Max, ei);
			for(int j = si; j <= ei; ++ j)
				addEdge(i, n + j, 1);
		}
		e = n + Max + 1;
		for(int i = 1; i <= Max; ++ i)
			addEdge(n + i, e, m);
		if(sap(s, e, e + 1) == sum)
			printf("Case %d: Yes\n\n", cas);
		else
			printf("Case %d: No\n\n", cas);
	}
	return 0;
}


你可能感兴趣的:(HDU 3572 Task Schedule(最大流问题,sap算法))