题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4406
题意:有M门课要复习,有N天的复习时间,每天都有K个课时可以复习,每门课有个基础分,每多学一课时该课程就增加一分,每天可学习的课程是给出的,现给出绩点的计算方法,一旦有一门课不及格绩点为为零,问所能达到的最高绩点
思路:每科的绩点函数f(x, w) = (4.0 - 3.0 * (100 - x) * (100 - x) / 1600) * w,不难看出这是费用与流量平方成正比的函数关系,故而使用白书上的拆边法建图,因为要尽量保证所有科目及格,故而对于那些基础分不够60的科目源点向其加边费用应为-INF,故而建图如下:
1.源点向每门课连边,若该课基础分 < 60,则先连一条容量为60 - 基础分费用为-INF的边以保证该课能优先达到60,然后对于61~100区间由源点到该门课连40条容量为1,费用为- (f(x +1, w) - f(x, w))的边,该课基础分 >= 60类似最后的处理方法
2.每门课与能复习该门课的那些天连边,容量为K费用为0
3.每一天向汇点连边容量为K费用为0
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <utility>
#include <cmath>
#include <queue>
#include <set>
#include <map>
#include <climits>
#include <functional>
#include <deque>
#include <ctime>
#include <string>
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int MAXN = 1000;
const int MAXM = 50000;
const int INF = 1e7;
typedef long long ll;
struct Edge
{
int to, next, cap, flow;
double cost;
} edge[MAXM];
int head[MAXN], tol;
int pre[MAXN];
double dis[MAXN];
bool vis[MAXN];
int N;//节点总个数,节点编号从0~N-1
void init(int n)
{
N = n;
tol = 0;
memset(head, -1, sizeof(head));
}
void addedge(int u, int v, int cap, double cost)
{
edge[tol].to = v;
edge[tol].cap = cap;
edge[tol].cost = cost;
edge[tol].flow = 0;
edge[tol].next = head[u];
head[u] = tol++;
edge[tol].to = u;
edge[tol].cap = 0;
edge[tol].cost = -cost;
edge[tol].flow = 0;
edge[tol].next = head[v];
head[v] = tol++;
}
bool spfa(int s, int t)
{
queue<int>q;
for (int i = 0; i < N; i++)
{
dis[i] = INF;
vis[i] = false;
pre[i] = -1;
}
dis[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)
{
int v = edge[i].to;
if (edge[i].cap > edge[i].flow &&
dis[v] > dis[u] + edge[i].cost )
{
dis[v] = dis[u] + edge[i].cost;
pre[v] = i;
if (!vis[v])
{
vis[v] = true;
q.push(v);
}
}
}
}
if (pre[t] == -1) return false;
else return true;
}
//返回的是最大流,cost存的是最小费用
int minCostMaxflow(int s, int t, double &cost)
{
int flow = 0;
cost = 0;
while (spfa(s, t))
{
int Min = INF;
for (int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to])
{
if (Min > edge[i].cap - edge[i].flow)
Min = edge[i].cap - edge[i].flow;
}
for (int i = pre[t]; i != -1; i = pre[edge[i ^ 1].to])
{
edge[i].flow += Min;
edge[i ^ 1].flow -= Min;
if (cost < edge[i].cost * Min + cost)
return 0;
else
cost += edge[i].cost * Min;
}
flow += Min;
}
return flow;
}
double cal(int x, int w)
{
return (4.0 - 3.0 * (100 - x) * (100 - x) / 1600) * w;
}
int cre[50], ba[50], a[50][50];
int main()
{
int n, m, k;
while (~scanf("%d%d%d", &n, &k, &m) && (n + m + k))
{
for (int i = 1; i <= m; i++)
scanf("%d", &cre[i]);
for (int i = 1; i <= m; i++)
scanf("%d", &ba[i]);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &a[i][j]);
int s = 0, t = n + m + 1;
init(t + 1);
for (int i = 1; i <= n; i++)
addedge(i + m, t, k, 0);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i][j])
addedge(j, i + m, k, 0);
for (int i = 1; i <= m; i++)
{
if (ba[i] < 60)
{
addedge(s, i, 60 - ba[i], -INF);
double p = cal(60, cre[i]);
for (int j = 61; j <= 100; j++)
{
double np = cal(j, cre[i]);
addedge(s, i, 1, -(np - p));
p = np;
}
}
else
{
double p = cal(ba[i], cre[i]);
addedge(s, ba[i], 1, p);
for (int j = ba[i] + 1; j <= 100; j++)
{
double np = cal(j, cre[i]);
addedge(s, i, 1, -(np - p));
p = np;
}
}
}
double cost;
minCostMaxflow(s, t, cost);
for (int i = head[s]; ~i; i = edge[i].next)
{
int v = edge[i].to;
if (edge[i].flow > 0)
ba[v] += edge[i].flow;
}
double sum = 0, ans = 0;
for (int i = 1; i <= m; i++)
sum += cre[i];
bool flag = false;
for (int i = 1; i <= m; i++)
{
if (ba[i] < 60)
{
flag = true;
break;
}
ans += cal(ba[i], cre[i]) / sum;
}
if (flag) ans = 0;
printf("%.6f\n", ans);
}
return 0;
}