HDU 6437 Problem L.Videos(最小费用流模板)

Problem L.Videos

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 356    Accepted Submission(s): 171


题目链接

Problem Description

C-bacteria takes charge of two kinds of videos: ’The Collection of Silly Games’ and ’The Collection of Horrible Games’.
For simplicity’s sake, they will be called as videoA and videoB.
There are some people who want to watch videos during today, and they will be happy after watching videos of C-bacteria.
There are n hours a day, m videos are going to be show, and the number of people is K.
Every video has a type(videoA or videoB), a running time, and the degree of happi- ness after someone watching whole of it.
People can watch videos continuous(If one video is running on 2pm to 3pm and another is 3pm to 5pm, people can watch both of them).
But each video only allows one person for watching.
For a single person, it’s better to watch two kinds to videos alternately, or he will lose W happiness.
For example, if the order of video is ’videoA, videoB, videoA, videoB, …’ or ’B, A, B, A, B, …’, he won’t lose happiness; But if the order of video is ’A, B, B, B, A, B, A, A’, he will lose 3W happiness.
Now you have to help people to maximization the sum of the degree of happiness.

Input

Multiple query.
On the first line, there is a positive integer T, which describe the number of data. Next there are T groups of data.
for each group, the first line have four positive integers n, m, K, W : n hours a day, m videos, K people, lose W happiness when watching same videos).
and then, the next m line will describe m videos, four positive integers each line S, T, w, op : video is the begin at S and end at T, the happiness that people can get is w, and op describe it’s tpye(op=0 for videoA and op=1 for videoB).
There is a blank line before each groups of data.
T<=20, n<=200, m<=200, K<=200, W<=20, 1<=S<T<=n, W<=w<=1000,
op=0 or op=1

Output

Your output should include T lines, for each line, output the maximum happiness for the corresponding datum.

Sample Input

 

2

10 3 1 10

1 5 1000 0

5 10 1000 1

3 9 10 0

10 3 1 10

1 5 1000 0

5 10 1000 0

3 9 10 0

Sample Output

 

2000

1990

 

 题意:

有n个小时,m个video,k个人,W的厌倦值

每个video在Si开始,Ti结束,看完一个video,人可以获得w的愉悦值,op表示这个video的种类

video有两种种类,A,B,如果一个人连续看了两场的同种类的video,那么这个人会减少W的愉悦值。

每一个video只能给一个人看,且只能被看一次。问所有人愉悦值总和的最大值
解析:

这题目是经典最小费用最大流问题

最小费用流 O(V * E * f)

* INIT: network g; g.build(v, e);

* CALL: g.mincost(s, t); flow=g.flow; cost=g.cost;

* 注意: SPFA增广, 实际复杂度远远小于O(V * E);

该题有两种建图的方法,一个是根据任务来建图,一个根据时间来建图

以下面这组样例为例

10 3 2 10

1 5 1000 0

5 10 1000 0

3 9 10 1

第一种方案是根据两个video时间有无冲突来建边,如果无冲突的话就建边,

并且如果两个video同种类,边的权值就是-W,否则是0。

流量表示人,所有点聚集到Y,Y再连向汇点,流量为K。

其他没有标注的边都是(1,0),1表示流量,0表示费用

#include<cstdio>

#include<cstring>

#include<algorithm>

#include<vector>

#include<queue>
#define INFINITE 1 << 26
#define MAX_NODE 1005
#define MAX_EDGE_NUM 40005
#define INF 0x3f3f3f3f
using namespace std;

 

const int MAXN = 500;

struct Edge{
    int to;
    int vol;
    int cost;
    int next;
};

 

Edge gEdges[MAXN*MAXN];
 
int gHead[MAXN];
int gPre[MAXN];
int gPath[MAXN];
int gDist[MAXN];
int n;
int video[MAXN][4];
 
int gEdgeCount;
void InsertEdge(int u, int v, int vol, int cost){
    gEdges[gEdgeCount].to = v;
    gEdges[gEdgeCount].vol = vol;
    gEdges[gEdgeCount].cost = cost;
    gEdges[gEdgeCount].next = gHead[u];
    gHead[u] = gEdgeCount++;
 
    gEdges[gEdgeCount].to = u;
    gEdges[gEdgeCount].vol = 0;         //vol为0,表示开始时候,该边的反向不通
    gEdges[gEdgeCount].cost = -cost;    //cost 为正向边的cost相反数,这是为了
    gEdges[gEdgeCount].next = gHead[v];
    gHead[v] = gEdgeCount++;
}

 

//假设图中不存在负权和环,SPFA算法找到最短路径/从源点s到终点t所经过边的cost之和最小的路径
bool Spfa(int s, int t){
    memset(gPre, -1, sizeof(gPre));
    memset(gDist, -INF, sizeof(gDist));
    gDist[s] = 0;
    queue<int> Q;
    Q.push(s);
    while (!Q.empty()){//由于不存在负权和环,因此一定会结束
        int u = Q.front();
        Q.pop();
 
        for (int e = gHead[u]; e != -1; e = gEdges[e].next){
            int v = gEdges[e].to;
            if (gEdges[e].vol > 0 && gDist[u] + gEdges[e].cost > gDist[v]){
                gDist[v] = gDist[u] + gEdges[e].cost;
                gPre[v] = u; //前一个点
                gPath[v] = e;//该点连接的前一个边
                Q.push(v);
            }
        }
    }
 
    if (gPre[t] == -1)  //若终点t没有设置pre,说明不存在到达终点t的路径
        return false;
    return true;
}
 
int MinCostFlow(int s, int t){
    int cost = 0;
    int flow = 0;
    while (Spfa(s, t)){
        int f = INFINITE;
        for (int u = t; u != s; u = gPre[u]){
            if (gEdges[gPath[u]].vol < f)
                f = gEdges[gPath[u]].vol;
        }
        flow += f;
        cost += gDist[t] ;
        for (int u = t; u != s; u = gPre[u]){
            gEdges[gPath[u]].vol -= f;   //正向边容量减少
            gEdges[gPath[u]^1].vol += f; //反向边容量增加
        }
    }
    return cost;
}

 

int main()
{

	int t;
	int Y,Final;
	scanf("%d",&t);
	int m,K,w;
	while(t--)
	{

		memset(gHead,-1,sizeof(gHead));

		gEdgeCount=0;

		scanf("%d%d%d%d",&m,&n,&K,&w);

		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%d%d",&video[i][0],&video[i][1],&video[i][2],&video[i][3]);

		}
		
		int a,b;
		Y=2*n+1;
		Final=2*n+2;
		InsertEdge(Y,Final,K,0);
		for(int i=1;i<=n;i++)
		{
			a=i;
			b=n+i;
			InsertEdge(0,i,1,0);
			InsertEdge(a,b,1,video[i][2]);
			InsertEdge(b,Y,1,0);

			for(int j=1;j<=n;j++)
			{
				if(i==j) continue;
				if(video[i][1]<=video[j][0])
				{
					if(video[i][3]==video[j][3])
					{
						InsertEdge(b,j,1,-w);
					}
					else
					{
						InsertEdge(b,j,1,0);
					}
				}
			}

		}

		
		int ans=MinCostFlow(0,Final);
		printf("%d\n",ans);

	}

}

第二种方案就是标程的方案,根据时间来建图

一开始就将一个时间分成两个点,一个在A,一个在B,那么我们就建立两条主链,一条是A的时间,一条是B的时间,都是n个点,从1-n,边的值都为(INF,0)

然后在建任务点,一个任务点根据种类选择起点,例如 1 5 1000 0,这个video,

我们就以A中1的时间点为起点连向新建的任务点,然后这个任务点再连两条边,

一条连向A的5时间点,值为(1,w-W),一条连向B的5时间点,值为(1,w)

这个图也是用流量表示人,一个人在两条主链上表示他在休息。而看完一个video,

有两条连出去的边是因为他接下来要看的video,他走向主链A的话,那么他接下来还是要看A,否则就是看B。

那么问题可能是一个人看完一个A,之后继续留在A的主链上,但是不看了,让另外的人看A的video,那么这个W是不是多减了呢?

事实上并不会这样,因为根据最小费用流的算法,如果你当前看完A,而另外有一个人在空闲,那么他一定会优先流向A后面的video。如果出现上面那种情况,那么你肯定是选择到B链的休息的。因为这样权值才会最大,

最大流里面就有反向边来处理这种情况的,如果你选的是A链,那么后面你不看,那么我们就会发现,你到B链休息的权值会更大。那么算法就会用反向边把到A链的流量退回来,然后再选择流向B链

下图为表明的边值都为(INF,0)

 

#include<cstdio>

#include<cstring>

#include<algorithm>

#include<vector>

#include<queue>
#define INFINITE 1 << 26
#define MAX_NODE 1005
#define MAX_EDGE_NUM 40005
#define INF 0x3f3f3f3f
using namespace std;



const int MAXN = 1e3;

struct Edge{
    int to;
    int vol;
    int cost;
    int next;
};



Edge gEdges[MAXN*MAXN*2];

int gHead[MAXN];
int gPre[MAXN];
int gPath[MAXN];
int gDist[MAXN];
int n;
int id[MAXN][2];

int gEdgeCount;
void InsertEdge(int u, int v, int vol, int cost){
    gEdges[gEdgeCount].to = v;
    gEdges[gEdgeCount].vol = vol;
    gEdges[gEdgeCount].cost = cost;
    gEdges[gEdgeCount].next = gHead[u];
    gHead[u] = gEdgeCount++;

    gEdges[gEdgeCount].to = u;
    gEdges[gEdgeCount].vol = 0;         //vol为0,表示开始时候,该边的反向不通
    gEdges[gEdgeCount].cost = -cost;    //cost 为正向边的cost相反数,这是为了
    gEdges[gEdgeCount].next = gHead[v];
    gHead[v] = gEdgeCount++;
}



//假设图中不存在负权和环,SPFA算法找到最短路径/从源点s到终点t所经过边的cost之和最小的路径
bool Spfa(int s, int t){
    memset(gPre, -1, sizeof(gPre));
    memset(gDist, -INF, sizeof(gDist));
    gDist[s] = 0;
    queue<int> Q;
    Q.push(s);
    while (!Q.empty()){//由于不存在负权和环,因此一定会结束
        int u = Q.front();
        Q.pop();

        for (int e = gHead[u]; e != -1; e = gEdges[e].next){
            int v = gEdges[e].to;
            if (gEdges[e].vol > 0 && gDist[u] + gEdges[e].cost > gDist[v]){
                gDist[v] = gDist[u] + gEdges[e].cost;
                gPre[v] = u; //前一个点
                gPath[v] = e;//该点连接的前一个边
                Q.push(v);
            }
        }
    }

    if (gPre[t] == -1)  //若终点t没有设置pre,说明不存在到达终点t的路径
        return false;
    return true;
}

int MinCostFlow(int s, int t){
    int cost = 0;
    int flow = 0;
    while (Spfa(s, t)){
        int f = INFINITE;
        for (int u = t; u != s; u = gPre[u]){
            if (gEdges[gPath[u]].vol < f)
                f = gEdges[gPath[u]].vol;
        }
        flow += f;
        cost += gDist[t] ;
        for (int u = t; u != s; u = gPre[u]){
            gEdges[gPath[u]].vol -= f;   //正向边容量减少
            gEdges[gPath[u]^1].vol += f; //反向边容量增加
        }
    }
    return cost;
}



int main()
{

	int t;
	int tot;
	int Y,Final;
	scanf("%d",&t);
	int m,K,w;
	while(t--)
	{
		memset(gHead,-1,sizeof(gHead));
        tot=0;
		gEdgeCount=0;

		scanf("%d%d%d%d",&m,&n,&K,&w);
		Y=++tot;
		for(int i=1;i<=m;i++)
		{
		    id[i][0]=++tot;
		    id[i][1]=++tot;
		}
		Final=++tot;
		InsertEdge(0,Y,K,0);
		InsertEdge(Y,id[1][0],INF,0);
		InsertEdge(Y,id[1][1],INF,0);
		for(int i=2;i<=m;i++)
        {
            InsertEdge(id[i-1][0],id[i][0],INF,0);
            InsertEdge(id[i-1][1],id[i][1],INF,0);
        }
        InsertEdge(id[m][0],Final,INF,0);
        InsertEdge(id[m][1],Final,INF,0);
        int a,b,c,d;
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d%d%d",&a,&b,&c,&d);
			int tmp=++tot;
            InsertEdge(id[a][d],tmp,1,0);
            InsertEdge(tmp,id[b][d],1,c-w);
            InsertEdge(tmp,id[b][d^1],1,c);
		}
		int ans=MinCostFlow(0,Final);
		printf("%d\n",ans);
	}
}

 最后再贴一个最小费用流的模板

#define INFINITE 1 << 26
#define MAX_NODE 1005
#define MAX_EDGE_NUM 40005
struct Edge{
    int to;
    int vol;
    int cost;
    int next;
};
Edge gEdges[MAX_EDGE_NUM];
 
int gHead[MAX_NODE];
int gPre[MAX_NODE];
int gPath[MAX_NODE];
int gDist[MAX_NODE];
 
int gEdgeCount;
void InsertEdge(int u, int v, int vol, int cost){
    gEdges[gEdgeCount].to = v;
    gEdges[gEdgeCount].vol = vol;
    gEdges[gEdgeCount].cost = cost;
    gEdges[gEdgeCount].next = gHead[u];
    gHead[u] = gEdgeCount++;
 
    gEdges[gEdgeCount].to = u;
    gEdges[gEdgeCount].vol = 0;         //vol为0,表示开始时候,该边的反向不通
    gEdges[gEdgeCount].cost = -cost;    //cost 为正向边的cost相反数,这是为了
    gEdges[gEdgeCount].next = gHead[v];
    gHead[v] = gEdgeCount++;
}
 
//假设图中不存在负权和环,SPFA算法找到最短路径/从源点s到终点t所经过边的cost之和最小的路径
bool Spfa(int s, int t){
    memset(gPre, -1, sizeof(gPre));
    memset(gDist, 0x7F, sizeof(gDist));
    gDist[s] = 0;
    queue<int> Q;
    Q.push(s);
    while (!Q.empty()){//由于不存在负权和环,因此一定会结束
        int u = Q.front();
        Q.pop();
 
        for (int e = gHead[u]; e != -1; e = gEdges[e].next){
            int v = gEdges[e].to;
            if (gEdges[e].vol > 0 && gDist[u] + gEdges[e].cost < gDist[v]){
                gDist[v] = gDist[u] + gEdges[e].cost;
                gPre[v] = u; //前一个点
                gPath[v] = e;//该点连接的前一个边
                Q.push(v);
            }
        }
    }
 
    if (gPre[t] == -1)  //若终点t没有设置pre,说明不存在到达终点t的路径
        return false;
    return true;
}
 
int MinCostFlow(int s, int t){
    int cost = 0;
    int flow = 0;
    while (Spfa(s, t)){
        int f = INFINITE;
        for (int u = t; u != s; u = gPre[u]){
            if (gEdges[gPath[u]].vol < f)
                f = gEdges[gPath[u]].vol;
        }
        flow += f;
        cost += gDist[t] * f;
        for (int u = t; u != s; u = gPre[u]){
            gEdges[gPath[u]].vol -= f;   //正向边容量减少
            gEdges[gPath[u]^1].vol += f; //反向边容量增加
        }
    }
    return cost;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值