还算是经典题目吧~

其实就快想出来了,但是突然脑残了一下,很烦

首 先 看 到 最 后 得 分 只 和 每 门 课 程 的 p i 有 关 首先看到最后得分只和每门课程的p_i有关 pi

化 简 p i 得 式 子 得 到 化简p_i得式子得到 pi

− 3 x 2 + 600 x − 23600 1600 \frac{-3x^2+600x-23600}{1600} 16003x2+600x23600

打 表 发 现 分 子 是 一 个 单 增 函 数 打表发现分子是一个单增函数

即 f ( x ) = − 3 x 2 + 600 x − 23600 单 增 即f(x)=-3x^2+600x-23600单增 f(x)=3x2+600x23600

且 f ( x ) − f ( x − 1 ) 单 增 ( 可 以 求 导 证 , 可 以 打 表 ) 且f(x)-f(x-1)单增(可以求导证,可以打表) f(x)f(x1)(,)

所 以 可 以 把 每 门 课 程 连 向 u 分 值 , 费 用 是 u 分 收 益 − ( u − 1 ) 的 收 益 所以可以把每门课程连向u分值,费用是u分收益-(u-1)的收益 u,u(u1)

根 据 费 用 流 贪 心 原 则 , 走 u 这 条 路 之 前 一 定 把 [ 0 , u − 1 ] 走 了 个 遍 根据费用流贪心原则,走u这条路之前一定把[0,u-1]走了个遍 ,u[0,u1]

这 就 满 足 了 分 数 不 会 跳 跃 这就满足了分数不会跳跃

具体连边(把边权费用取反做最小费用最大流)

每 天 向 汇 点 连 一 条 流 量 k , 费 用 0 的 边 每天向汇点连一条流量k,费用0的边 k,0

若 分 数 小 于 60 , 源 点 向 学 科 连 一 条 流 量 60 − a i , 费 用 − i n f 的 边 若分数小于60,源点向学科连一条流量60-a_i,费用-inf的边 60,60ai,inf

表示强制及格

然 后 源 点 再 向 汇 点 连 40 条 边 , 每 条 边 流 量 1 , 费 用 f ( u ) − f ( u − 1 ) , 表 示 分 值 然后源点再向汇点连40条边,每条边流量1,费用f(u)-f(u-1),表示分值 40,1,f(u)f(u1),

若 分 数 大 于 等 于 60 若分数大于等于60 60

源 点 向 学 科 连 100 − a i 条 边 , 流 量 1 , 费 用 f ( u ) − f ( u − 1 ) 源点向学科连100-a_i条边,流量1,费用f(u)-f(u-1) 100ai,1,f(u)f(u1)

#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;
	}
}