网络流/费用流
orz zyf
裸的费用流,根据题目描述即可建出如下的图:
S->i 费用表示每有一个加入第 i 个小组的学生,需要花的钱,由于是跟流量(人数)的二次方相关,所以要拆边……然后每个人的报名费直接用支出减去即可(也就是每条边的费用都减去一个常量)
i->j+m 根据矩阵连边……如果第 j 个学生能报名第 i 个小组即连一条边,费用为0。
j+m->T 容量为k,费用为0,表示每个人最多报k个小组。
但是这题很坑啊!限制条件是参与学生尽量多,也就是说在一定会亏的时候每人最多只报一个小组……刚刚的建图,最小费用最大流行不通了!
我们想想,如果一个人报名了会使得总支出减少,那他肯定要报名某一个小组,如果不能减少的话……就让这个改变量为0好了!
所以我们对于每个人 j ,连边S->j+m (k-1,0)表示这个人最多可以有k-1个报名机会直接放弃掉!(因为要让参与人数尽量多啊……所以不能是k)
/**************************************************************
Problem: 3442
User: Tunix
Language: C++
Result: Accepted
Time:1560 ms
Memory:3624 kb
****************************************************************/ //BZOJ 3442
#include<vector>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define rep(i,n) for(int i=0;i<n;++i)
#define F(i,j,n) for(int i=j;i<=n;++i)
#define D(i,j,n) for(int i=j;i>=n;--i)
#define pb push_back
using namespace std;
inline int getint(){
int v=,sign=; char ch=getchar();
while(ch<''||ch>''){ if (ch=='-') sign=-; ch=getchar();}
while(ch>=''&&ch<=''){ v=v*+ch-''; ch=getchar();}
return v*sign;
}
const int N=,M=,INF=~0u>>;
typedef long long LL;
/******************tamplate*********************/
int n,m,k,ans,tmp,num,c[N];
struct edge{int from,to,v,c;};
struct Net{
edge E[M];
int head[N],next[M],cnt;
void ins(int x,int y,int z,int c){
E[++cnt]=(edge){x,y,z,c};
next[cnt]=head[x]; head[x]=cnt;
}
void add(int x,int y,int z,int c){
ins(x,y,z,c); ins(y,x,,-c);
}
int from[N],Q[M],d[N],S,T,ed;
bool inq[N],sign;
bool spfa(){
int l=,r=-;
F(i,,T) d[i]=INF;
d[S]=; Q[++r]=S; inq[S]=;
while(l<=r){
int x=Q[l++];
inq[x]=;
for(int i=head[x];i;i=next[i])
if(E[i].v> && d[x]+E[i].c<d[E[i].to]){
d[E[i].to]=d[x]+E[i].c;
from[E[i].to]=i;
if (!inq[E[i].to]){
Q[++r]=E[i].to;
inq[E[i].to]=;
}
}
}
return d[T]!=INF;
}
void mcf(){
int x=INF;
for(int i=from[T];i;i=from[E[i].from])
x=min(x,E[i].v);
for(int i=from[T];i;i=from[E[i].from]){
E[i].v-=x;
E[i^].v+=x;
}
ans+=x*d[T];
}
void init(){
n=getint(); m=getint(); k=getint(); cnt=;
S=; T=n+m+; num=sign=; tmp=-INF;
F(i,,m) c[i]=getint();
int x;
F(i,,m){
x=getint();
F(j,,n) add(S,i,,c[i]*(j*-)-x);
}
char s[];
F(i,,n) {
scanf("%s",s+);
F(j,,m){
x=s[j]-'';
if (x) add(j,i+m,,);
}
}
F(i,,n) add(S,i+m,k-,),add(i+m,T,k,);
while(spfa()) mcf();
printf("%d\n",ans);
}
}G1; int main(){
#ifndef ONLINE_JUDGE
freopen("3442.in","r",stdin);
freopen("3442.out","w",stdout);
#endif
G1.init();
return ;
}
3442: 学习小组
Time Limit: 5 Sec Memory Limit: 128 MB
Submit: 176 Solved: 73
[Submit][Status][Discuss]
Description
【背景】
坑校准备鼓励学生参加学习小组。
【描述】
共有n个学生,m个学习小组,每个学生有一定的喜好,只愿意参加其中的一些学习小组,但是校领导为学生考虑,规定一个学生最多参加k个学习小组。财务处的
大叔就没那么好了,他想尽量多收钱,因为每个学生参加学习小组都要交一定的手续费,不同的学习小组有不同的手续费。然而,事与愿违,校领导又决定对学习小
组组织者进行奖励,若有a个学生参加第i个学习小组,那么给这个学习小组组织者奖励Ci*a^2元。在参与学生(而不是每个学习小组的人数总和)尽量多的
情况下,求财务处最少要支出多少钱(若为负数,则输出负数)(支出=总奖励费-总手续费)。
Input
输
入有若干行,第一行有三个用空格隔开的正整数n、m、k。接下来的一行有m个正整数,表示每个Ci。第三行有m个正整数,表示参加每个学习小组需要交的手
续费Fi。再接下来有一个n行m列的矩阵,表若第i行j列的数字是1,则表示第i个学生愿意参加第j个学习小组,若为0,则为不愿意。
入有若干行,第一行有三个用空格隔开的正整数n、m、k。接下来的一行有m个正整数,表示每个Ci。第三行有m个正整数,表示参加每个学习小组需要交的手
续费Fi。再接下来有一个n行m列的矩阵,表若第i行j列的数字是1,则表示第i个学生愿意参加第j个学习小组,若为0,则为不愿意。
Output
输出只有一个整数,为最小的支出。
Sample Input
3 3 1
1 2 3
3 2 1
111
111
111
1 2 3
3 2 1
111
111
111
Sample Output
-2
【样例解释】
参与学生最多为3,每个学生参加一个学习小组,若有两个学生参加第一个学习小组,一个学生参加第二个学习小组(一定要有人参加第二个学习小组),支出为-2,可以证明没有更优的方案了。
【数据范围与约定】
100%的数据,0<n≤100,0<m≤90,0<k≤m,0<Ci≤10,0<Fi≤100。
【样例解释】
参与学生最多为3,每个学生参加一个学习小组,若有两个学生参加第一个学习小组,一个学生参加第二个学习小组(一定要有人参加第二个学习小组),支出为-2,可以证明没有更优的方案了。
【数据范围与约定】
100%的数据,0<n≤100,0<m≤90,0<k≤m,0<Ci≤10,0<Fi≤100。