網路流之最大費用流
傳送門
題意
現在有 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