题目大意:给你一些学科和现在已经获得的分数,然后每天可以让一些学科涨一定的分数,但是每天涨的总和一定,并给你一个二次算分公式,问你规定天数后最大学分是多少。
思路:首先要保证及格,如果某一课的基础分a小于60,从源点连a到该科,费用为-INF,这样就保证了先增广缺少的分数。然后对于60分以上的情况,费用与流量平方成正比,我们可以用拆边法,LRJ白书P366上有。
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define REP( i, a, b ) for( int i = a; i < b; i++ )
#define FOR( i, a, b ) for( int i = a; i <= b; i++ )
#define CLR( a, x ) memset( a, x, sizeof a )
#define CPY( a, x ) memcpy( a, x, sizeof a )
const int maxn = 1000 + 10;
const int maxe = 10000;
const int INF = 0x3f3f3f3f;
struct Edge{
int v, c, f;
double w;
int next;
Edge() {}
Edge(int v, int c, int f, double w, int next) : v(v), c(c), f(f), w(w), next(next) {}
};
struct MCMF{
int n, s, t;
int cur[maxn], f[maxn];
int Head[maxn], cntE;
int Q[maxn], head, tail, inq[maxn];
int flow;
double cost;
double d[maxn];
Edge edge[maxe];
void Init(int n){
this -> n = n;
cntE = 0;
CLR(Head, -1);
}
void Add(int u, int v, int c, double w){
edge[cntE] = Edge(v, c, 0, w, Head[u]);
Head[u] = cntE++;
edge[cntE] = Edge(u, 0, 0, -w, Head[v]);
Head[v] = cntE++;
}
int Spfa(){
head = tail = 0;
for(int i = 0; i < n; i++) d[i] = (double)INF;
CLR(inq, 0);
Q[tail++] = s;
f[s] = INF;
cur[s] = -1;
d[s] = 0;
while(head != tail){
int u = Q[head++];
if(head == maxn) head = 0;
inq[u] = 0;
for(int i = Head[u]; ~i; i = edge[i].next){
int v = edge[i].v;
if(edge[i].c > edge[i].f && d[v] > d[u] + edge[i].w){
f[v] = min(f[u], edge[i].c - edge[i].f);
d[v] = d[u] + edge[i].w;
cur[v] = i;
if(!inq[v]){
if(d[v] < d[Q[head]]){
if(head == 0) head = maxn;
Q[--head] = v;
}
else{
Q[tail++] = v;
if(tail == maxn) tail = 0;
}
inq[v] = 1;
}
}
}
}
if(d[t] == INF) return 0;
flow += f[t];
cost += f[t] * d[t];
for(int i = cur[t]; ~i; i = cur[edge[i^1].v]){
edge[i].f += f[t];
edge[i^1].f -= f[t];
}
return 1;
}
double Mcmf(int s, int t){
this -> s = s;
this -> t = t;
flow = 0;
cost = 0;
while(Spfa());
return cost;
}
}solver;
int n, k, m;
int w[maxn], basic[maxn];
int can_learn[maxn][maxn];
int s[maxn];
double f(int i, int x){
return (-3.0 * (200 - 2 * x + 1) / 1600) * w[i];
}
void solve(){
memset(can_learn, 0, sizeof(can_learn));
memset(s, 0, sizeof(s));
int W = 0;
FOR(i, 1, m) scanf("%d", &w[i]), W += w[i];
FOR(i, 1, m) scanf("%d", &basic[i]);
FOR(i, 1, n) FOR(j, 1, m) scanf("%d", &can_learn[i][j]);
int S = 0, T = m + n + 1;
solver.Init(T + 1);
FOR(i, 1, m){
int add = 0;
if(basic[i] < 60){
add = 60 - basic[i];
solver.Add(S, i, add, -double(INF));
}
FOR(j, basic[i] + add + 1, 100){
solver.Add(S, i, 1, f(i, j));
}
FOR(j, 1, n)if(can_learn[j][i]){//第i门课在第j天可学习
solver.Add(i, m+j, 100 - basic[i], 0);
}
}
FOR(i, 1, n) solver.Add(m + i, T, k, 0);
solver.Mcmf(S, T);
double ans = 0;
for(int i = solver.Head[S]; ~i; i = solver.edge[i].next){
if(solver.edge[i].c == 0) continue;
int c = solver.edge[i].v;
s[c] += solver.edge[i].f;
}
int ok = 1;
FOR(i, 1, m)if(basic[i] + s[i] >= 60) ans += (4 - 3.0 * (100 - basic[i] - s[i]) * (100 - basic[i] - s[i]) / 1600 ) * w[i];
FOR(i, 1, m) if(basic[i] + s[i] < 60){
ok = 0;
break;
}
if(!ok) printf("0.000000\n");
else printf("%lf\n", ans / W);
}
int main()
{
while(scanf("%d%d%d", &n, &k, &m)){
if(n == 0 && k == 0 && m == 0)
break;
solve();
}
return 0;
}
本文介绍了一种基于最大费用流算法解决特定问题的方法。通过构建一个包含学科、分数和学习计划的模型,利用拆边法处理费用与流量之间的二次关系,实现对最优解的求解。
2491

被折叠的 条评论
为什么被折叠?



