还算是经典题目吧~
其实就快想出来了,但是突然脑残了一下,很烦
首 先 看 到 最 后 得 分 只 和 每 门 课 程 的 p i 有 关 首先看到最后得分只和每门课程的p_i有关 首先看到最后得分只和每门课程的pi有关
化 简 p i 得 式 子 得 到 化简p_i得式子得到 化简pi得式子得到
− 3 x 2 + 600 x − 23600 1600 \frac{-3x^2+600x-23600}{1600} 1600−3x2+600x−23600
打 表 发 现 分 子 是 一 个 单 增 函 数 打表发现分子是一个单增函数 打表发现分子是一个单增函数
即 f ( x ) = − 3 x 2 + 600 x − 23600 单 增 即f(x)=-3x^2+600x-23600单增 即f(x)=−3x2+600x−23600单增
且
f
(
x
)
−
f
(
x
−
1
)
单
增
(
可
以
求
导
证
,
可
以
打
表
)
且f(x)-f(x-1)单增(可以求导证,可以打表)
且f(x)−f(x−1)单增(可以求导证,可以打表)
所
以
可
以
把
每
门
课
程
连
向
u
分
值
,
费
用
是
u
分
收
益
−
(
u
−
1
)
的
收
益
所以可以把每门课程连向u分值,费用是u分收益-(u-1)的收益
所以可以把每门课程连向u分值,费用是u分收益−(u−1)的收益
根 据 费 用 流 贪 心 原 则 , 走 u 这 条 路 之 前 一 定 把 [ 0 , u − 1 ] 走 了 个 遍 根据费用流贪心原则,走u这条路之前一定把[0,u-1]走了个遍 根据费用流贪心原则,走u这条路之前一定把[0,u−1]走了个遍
这 就 满 足 了 分 数 不 会 跳 跃 这就满足了分数不会跳跃 这就满足了分数不会跳跃
具体连边(把边权费用取反做最小费用最大流)
每 天 向 汇 点 连 一 条 流 量 k , 费 用 0 的 边 每天向汇点连一条流量k,费用0的边 每天向汇点连一条流量k,费用0的边
若 分 数 小 于 60 , 源 点 向 学 科 连 一 条 流 量 60 − a i , 费 用 − i n f 的 边 若分数小于60,源点向学科连一条流量60-a_i,费用-inf的边 若分数小于60,源点向学科连一条流量60−ai,费用−inf的边
表示强制及格
然 后 源 点 再 向 汇 点 连 40 条 边 , 每 条 边 流 量 1 , 费 用 f ( u ) − f ( u − 1 ) , 表 示 分 值 然后源点再向汇点连40条边,每条边流量1,费用f(u)-f(u-1),表示分值 然后源点再向汇点连40条边,每条边流量1,费用f(u)−f(u−1),表示分值
若 分 数 大 于 等 于 60 若分数大于等于60 若分数大于等于60
源 点 向 学 科 连 100 − a i 条 边 , 流 量 1 , 费 用 f ( u ) − f ( u − 1 ) 源点向学科连100-a_i条边,流量1,费用f(u)-f(u-1) 源点向学科连100−ai条边,流量1,费用f(u)−f(u−1)
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=2e5+10;
const int inf=1e17;
const double eps=1e-7;
int n,m,s,t,k;
double maxflow,mincost;
int head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
double w[maxn],dis[maxn],a[maxn];
struct edge{
int to,nxt,flow; double w;//分别代表
}d[maxn<<1];
void add(int u,int v,int flow,double w)//最大流量,单位费用
{
w=-w;
d[++cnt]=(edge){v,head[u],flow,w},head[u]=cnt;
d[++cnt]=(edge){u,head[v],0,-w},head[v]=cnt;
}
bool spfa()
{
queue<int>q;
for(int i=0;i<=t;i++) dis[i]=inf,vis[i]=0;
q.push(s);
dis[s]=0,vis[s]=1;
incf[s] = inf;//初始流量无限大
while( !q.empty() )
{
int u=q.front(); q.pop();
vis[u]=0;//出队
for(int i=head[u];i;i=d[i].nxt)
{
if( !d[i].flow ) continue;//无流量了
int v=d[i].to;
if( dis[v]>dis[u]+d[i].w+eps )
{
dis[v]=dis[u]+d[i].w;
incf[v] = min(incf[u],d[i].flow);//更新当前流量
pre[v]=i;//记录从哪条边过来的
if( !vis[v] ) vis[v]=1,q.push(v);
}
}
}
if( dis[t]==inf ) return 0;
return 1;
}
void dinic()
{
while( spfa() )
{
int x=t;//倒回去找路径
maxflow+=incf[t];
mincost+=dis[t]*incf[t];
int i;
while(x != s)
{
i=pre[x];
d[i].flow-=incf[t];//减去流量
d[i^1].flow+=incf[t];//加上流量
x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去
}
}
}
double f(double g,int x)
{
return g*(-3*x*x+600*x-23600);
}
signed main()
{
while( cin >> n >> k >> m&&(n+k+m) )
{
s=0,t=n+m+1;
double ans=0,sumn=0;
for(int i=1;i<=m;i++) cin >> w[i],sumn+=w[i];
for(int i=1;i<=n;i++) add(i+m,t,k,0);
for(int i=1;i<=m;i++)
{
cin >> a[i];
if( a[i]<60 )
{
add(s,i,60-a[i],1e7*1.0);//必走
for(int j=61;j<=100;j++)
add(s,i,1,f(w[i],j)-f(w[i],j-1) );
}
else
{
for(int j=a[i]+1;j<=100;j++)
add(s,i,1,f(w[i],j)-f(w[i],j-1) );
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
int x; cin >> x;
if( x ) add(j,i+m,k,0);
}
dinic();
int flag=1;
for(int i=head[s];i;i=d[i].nxt )
a[d[i].to]+=d[i^1].flow;
for(int i=1;i<=m;i++)
if( a[i]<60 ) flag=0;
else ans+=f(w[i],a[i]);
if( flag )
{
printf("%.6lf\n",ans/1600.0/sumn);
mincost=0;
}
else printf("0.000000\n");
cnt=1;
for(int i=0;i<=t;i++) head[i]=0;
}
}