HDU 4322 Candy (最小费用最大流)

                                              Candy

Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 2333    Accepted Submission(s): 709


Problem Description
There are N candies and M kids, the teacher will give this N candies to the M kids. The i-th kids for the j-th candy has a preference for like[i][j], if he like the sugar, like[i][j] = 1, otherwise like[i][j] = 0. If the i-th kids get the candy which he like he will get K glad value. If he or she do not like it. He will get only one glad value. We know that the i-th kids will feel happy if he the sum of glad values is equal or greater than B[i]. Can you tell me whether reasonable allocate this N candies, make every kid feel happy.
 

Input
The Input consists of several cases .The first line contains a single integer t .the number of test cases.
For each case starts with a line containing three integers N, M, K (1<=N<=13, 1<=M<=13, 2<=K<=10)
The next line contains M numbers which is B[i](0<=B[i]<=1000). Separated by a single space.
Then there are M*N like[i][j] , if the i-th kids like the j-th sugar like[i][j]=1 ,or like[i][j]=0.
 

Output
If there have a reasonable allocate make every kid feel happy, output "YES", or "NO".
 

Sample Input
 
  
2 3 2 2 2 2 0 0 0 0 0 1 3 2 2 2 2 0 0 0 0 0 0
 

Sample Output
 
  
Case #1: YES Case #2: NO
Hint
Give the first and second candy to the first kid. Give the third candy to the second kid. This allocate make all kids happy.
 

一、原题地址

        传送门


二、大致题意

    现在有n颗糖需要分配给m个小朋友。每个小朋友对每颗糖有自己的喜好,用一个 01 矩阵来表示(1表示喜欢)。

如果一个小朋友被分到了一颗喜欢的糖,那么他会获得 K 点愉悦值,若分配到一颗不喜欢的糖则只得到一点愉悦值。

给出一组小朋友的开心值B[i],若某个小朋友的愉悦值达到了他对应的B[i],则他就会处于开心状态。

现在询问是否能使得所有的小朋友都处于开心状态。输出YES或者NO。


三、思路

    对于分配的问题,想到的就是网络流。那么如何来建图呢。

    首先建立一个超级源点,使它指向所有的糖,显然容量应为1。

    再将所有的小朋友指向一个超级汇点,因为我们知道每个小盆友的开心值为Bi,并且也知道每一颗喜欢的糖可以为这个小朋友提供 K 点愉悦值,那么也就是说 Bi/K 就是这个小朋友应该被分配的喜欢的糖的个数(也就是这条边的容量)。但是Bi/K不一定是整数,那么此时我们就会陷入疑问,是再分一颗糖给他呢?还是把这颗糖分配给同样喜欢这颗糖的小朋友?所以我们意识到仅仅只利用最大流显然是不能解决这个问题的,此时只要再给每条边引入一个费用,那么我们就可以得到糖的分配优先级。

   1、 对于Bi/K为整数的情况,我们肯定要优先这样分配,显然他的费用应该为最小,他的容量也就是Bi/K。

   2、对于Bi/K不是整数的情况,若Bi%K的值越大,就越优先分糖,因为若不给这个小朋友分糖,那么他在之后拿取价值为1的糖时,将会占用更多的资源。

    这样我们就建立了一个只分配喜欢的糖为依据的最小费用最大流。为什么只关于被喜欢的糖来建图呢,因为这样能保证这些糖的价值得到最好的使用,而那些不被喜欢的糖只能提供1的价值,我们是可以用作弥补分糖时部分小朋友留下的漏洞。

    根据以上思路,我们可以用 rest 来记录跑完最小费用最大流之后还剩下的价值为1的糖的数量,然后我们从汇点遍历与他相邻的边,即与每个小朋友的流量,可以得到每个小朋友当前分得的喜欢的糖的个数记作use[i],那么他们当前的愉悦值就是use[i]*K。    之后就是检查一下剩下的糖数能否补全他们的愉悦值,然后输出判断就可以啦。


四、代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<string>
#include<algorithm>
#include<vector>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<list>
using namespace std;
const int INF = 0x3f3f3f3f;
#define LL long long int 
#define PI 3.14159265
long long  gcd(long long  a, long long  b) { return a == 0 ? b : gcd(b % a, a); }

const int MAXN = 1500;
const int MAXM = 10000;
int Case = 1;
int n, m, K, T;
int like[15][15], B[15];
int all = 0;
int vs, vt, mincost,maxflow;	//源点、汇点、最小费用、最大流
struct Edge
{
	int from, to, cap, flow, cost, next;
};
Edge edge[MAXM];
int head[MAXN], edgenum;
int pre[MAXN];//记录增广路径上 到达点i的边的编号
int dist[MAXN];
bool vis[MAXN];
int N, M;		//点数 边数
void init()
{
	edgenum = 0;
	memset(head, -1, sizeof(head));
}
void addEdge(int u, int v, int w, int c)
{
	Edge E1 = { u, v, w, 0, c, head[u] };
	edge[edgenum] = E1;
	head[u] = edgenum++;
	Edge E2 = { v, u, 0, 0, -c, head[v] };
	edge[edgenum] = E2;
	head[v] = edgenum++;
}
bool SPFA(int s, int t)//寻找花销最少的路径
{
	//跑一遍SPFA 找s——t的最少花销路径 且该路径上每一条边不能满流
	//若存在 说明可以继续增广,反之不能
	queue<int> Q;
	memset(dist, INF, sizeof(dist));
	memset(vis, false, sizeof(vis));
	memset(pre, -1, sizeof(pre));
	dist[s] = 0;
	vis[s] = true;
	Q.push(s);
	while (!Q.empty())
	{
		int u = Q.front();
		Q.pop();
		vis[u] = false;
		for (int i = head[u]; i != -1; i = edge[i].next)
		{
			Edge E = edge[i];
			if (dist[E.to] > dist[u] + E.cost && E.cap > E.flow)//可以松弛 且 没有满流
			{
				dist[E.to] = dist[u] + E.cost;
				pre[E.to] = i;//记录前驱边 的编号
				if (!vis[E.to])
				{
					vis[E.to] = true;
					Q.push(E.to);
				}
			}
		}
	}
	return pre[t] != -1;//可达返回true
}
void MCMF(int s, int t, int &cost, int &flow)
{
	flow = 0;//总流量
	cost = 0;//总费用
	while (SPFA(s, t))//每次寻找花销最小的路径
	{
		int Min = INF;
		//通过反向弧 在源点到汇点的最少花费路径 找最小增广流
		for (int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to])
		{
			Edge E = edge[i];
			Min = min(Min, E.cap - E.flow);
		}
		//增广
		for (int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to])
		{
			edge[i].flow += Min;
			edge[i ^ 1].flow -= Min;
			cost += edge[i].cost * Min;//增广流的花销
		}
		flow += Min;//总流量累加
	}
}
void read()
{
	init();
	scanf("%d %d %d", &n, &m, &K);
	for (int i = 1; i <= m; i++)
	{
		scanf("%d", &B[i]);
		all += B[i];
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			scanf("%d", &like[i][j]);
		}
	}
}
void build()
{
	vs = 0;
	vt = n + m + 1;
	for (int i = 1; i <= n; i++)
		addEdge(vs, i, 1, 0);
	for (int i = n + 1; i <= n + m; i++)
	{
		int temp = B[i-n] / K;
		addEdge(i, vt, temp, 0);
		if (B[i-n] % K != 0)
		{
			addEdge(i, vt, 1, K-B[i-n] % K);
		}
	}
	for (int i = 1; i <= m; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			if (like[i][j])
			{
				addEdge(j, i + n, 1, 0);
			}
		}
	}
}
void work()
{
	read();
	build();
	int use[50];
	memset(use, 0, sizeof(use));
	MCMF(vs, vt, mincost,maxflow);
	int rest = n - maxflow;
	for (int i = head[vt]; i != -1; i = edge[i].next)
	{
		int u = edge[i].to;
		use[u] += edge[i ^ 1].flow;				//得到增广路上的流量
	}
	bool flag = true;
	for (int i = n + 1; i <= n + m; i++)
	{
		if (use[i] * K < B[i - n])
		{
			rest -= (B[i - n] - use[i] * K);
		}
		if (rest < 0)
		{
			flag = false;
			break;
		}
	}
	printf("Case #%d: ", Case++);
	if (flag)printf("YES\n");
	else printf("NO\n");
}
int main() 
{
	scanf("%d", &T);
	while (T--)
	{
		work();
	}
	getchar();
	getchar();
} 

猜你喜欢

转载自blog.csdn.net/amovement/article/details/80917551