/*
题意:用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算法)
最新推荐文章于 2018-04-10 22:27:21 发布