Candy
Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 2333 Accepted Submission(s): 709
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.
一、原题地址
二、大致题意
现在有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();
}