传送门
题意
现在有 n 颗糖果,要将其分给 m 个小朋友,每个小朋友都有特定的喜好,如果他得到了自己喜欢的糖果,那么他将增加K的欢乐值,否则就只会增加1的欢乐值。当第 i 个小朋友的欢乐值大于等于Bi时,他才是高兴的
问是否存在一种分配方案,使得所有小朋友都高兴
分析
这这这,要是不讲,我是完全看不出来网络流的啊……怎么那么菜……
首先,因为要使所有小朋友都高兴,那我们肯定尽量的给每一个小朋友他喜欢的糖果,现在就来考虑这样的特殊糖果
建立一个源点
从源点往每一个糖果连一条流量为1,费用为0的边(流量限制使用次数,费用在这儿暂时没用)
每一个糖果往喜欢他的小朋友那里连边,连流量为1,费用为0 的边
再从每一个小朋友往汇点连边
然后难点就来了,这往汇点连的边该如何限制才能保证一条流跑下来是合法的呢?
边:容量是 b[i] / k ,费用是 k ,
因为我们知道一个小朋友的欢乐值要大于等于Bi,就先尽量用这个小朋友喜欢的糖果去搞。由于这里的建图只涉及了特殊糖果,那么这些边的流量限制就是 ,说明对于第i个小朋友只能选个特殊糖果。因为如果还要选的话就会造成浪费,我们当然不希望这样的情况发生
但如果b[i]%k!=0,说明光用特殊糖果不能刚好填完这个小朋友的欢乐值,我们就还需要一些糖果
再分细一点:
当b[i] % k == 1时,此后再选的话,特殊糖和普通的糖无异,没必要纳入考虑。
如果>1,这时候就需要再建第二条边(j,t,1,b[i]%k),容量是1,费用是b[i]%k,它的意义就是用一个喜欢的糖果把b[i]%k的部分填补掉。
注意这样的正确性:孩子和汇点间的第二条边连接时,费用是b[i]%k,因为如果是k的话,在最后判断时相当于“会将多出来的那部分欢乐值”分给别的孩子。但实际上多出来那部分是浪费的,所以正确的是添加费用为b[i]%k的边。
但这时候再考虑一个问题,就是如果你套最小费用最大流的板子,它肯定会先走第二条边,但这样是不对的。这样的话就有可能导致费用是k的边的容量有剩余时,而第二条边已经被流了,道理自己想吧。所以我们需要做的就是先让它流费用大的,所以这是个最大费用最大流。只需要把费用取相反数,流完再取反就好了。
最后判断n-ans>=all(b[i])-cost是否成立就好了,也就是剩余的没人喜欢(或者被某些孩子喜欢但这个孩子快乐度够了而不能要)的糖果是不是能满足剩余的快乐度。这个讲得好
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<queue>
#define in read()
#define M 4000000
#define inf 10000000
#define N 500
using namespace std;
inline int read(){
char ch;int f=1,res=0;
while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
while(ch>='0'&&ch<='9'){
res=(res<<3)+(res<<1)+ch-'0';
ch=getchar();
}
return f==1?res:-res;
}
int n,f,d,S=0,T;
int nxt[M],to[M],cap[M],head[N],cur[N],cnt=1,lev[N],w[M];
void add(int x,int y,int z,int ww){
nxt[++cnt]=head[x];head[x]=cnt;to[cnt]=y;cap[cnt]=z;w[cnt]=ww;
nxt[++cnt]=head[y];head[y]=cnt;to[cnt]=x;cap[cnt]=0;w[cnt]=-ww;
}
int dis[N],walk[N],vis[N];
bool spfa(){
for(int i=S;i<=T;++i){
cur[i]=head[i];dis[i]=inf;
walk[i]=0;vis[i]=0;
}
queue<int > q;
q.push(S);vis[S]=1;dis[S]=0;
while(!q.empty()){
int u=q.front();q.pop();vis[u]=0;
for(int e=head[u];e;e=nxt[e]){
int v=to[e];
if(dis[u]+w[e]<dis[v]&&cap[e]>0){
dis[v]=dis[u]+w[e];
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
}
if(dis[T]!=inf) return true;
return false;
}
int cost=0;
int dinic(int u,int flow){
if(u==T){
cost+=flow*dis[T];
return flow;
}
int delta,res=0;
walk[u]=1;
for(int &e=cur[u];e;e=nxt[e]){
int v=to[e];
if(dis[v]==dis[u]+w[e]&&cap[e]>0&&!walk[v]){
delta=dinic(v,min(cap[e],flow-res));
if(delta){
res+=delta;cap[e]-=delta;
cap[e^1]+=delta;if(res==flow) return flow;
}
}
}
return res;
}
int t,m,k;
int b[20];
int main(){
t=in;int tt=0;
while(t--){
tt++;cost=0;
memset(head,0,sizeof(head));
n=in;m=in;k=in;
T=n+m+1;S=0;
int i,j,tot=0;
for(i=1;i<=n;++i) add(S,i,1,0);
for(i=1;i<=m;++i) b[i]=in,tot+=b[i];
for(i=1;i<=m;++i)
for(j=1;j<=n;++j)
{
int p=in;
if(p==1) add(j,i+n,1,0);
}
for(i=1;i<=m;++i){
int ii=i+n;
add(ii,T,b[i]/k,-k);
if(b[i]%k>1) add(ii,T,1,-b[i]%k);
}
int maxflow=0;
while(spfa()) maxflow+=dinic(S,inf);
cost=-cost;
if(n-maxflow>=tot-cost) printf("Case #%d: YES\n",tt);
else printf("Case #%d: NO\n",tt);
}
return 0;
}
突然发现最小流板子居然忘了……补补bububububu