2012年多校的一道费用流,令人叹为观止的贪心建图思想
首先一块糖果给不同人的收益是不同的,所以考虑费用流而不是最大流
然后就不会做了…
发现糖果要么贡献价值 k k k,要么贡献价值 1 1 1
如果我们让尽量多的糖果贡献价值 k k k,是不是意味着最优呢?
大概是这样,但也不全是,因为有的人会溢出收益
考虑某个人的 b i b_i bi,如果 b i % k = = 0 b_i\%k==0 bi%k==0,那么他最多能吃掉 b i / k b_i/k bi/k个喜欢的糖果
那 么 这 个 人 向 汇 点 连 一 条 流 量 b i / k , 费 用 k 的 边 那么这个人向汇点连一条流量b_i/k,费用k的边 那么这个人向汇点连一条流量bi/k,费用k的边
如果 b i % k ! = 0 b_i\%k!=0 bi%k!=0,那么他最多吃掉 b i / k + 1 b_i/k+1 bi/k+1个糖果,但这个时候稍有变化
最后一枚糖果的收益溢出了,那我们应该减少最后一枚糖果的收益
所 以 这 个 人 向 汇 点 连 一 条 流 量 b i / k , 费 用 k 的 边 所以这个人向汇点连一条流量b_i/k,费用k的边 所以这个人向汇点连一条流量bi/k,费用k的边
再 向 汇 点 额 外 连 一 条 流 量 1 , 费 用 b i % k 的 边 再向汇点额外连一条流量1,费用b_i\%k的边 再向汇点额外连一条流量1,费用bi%k的边
这样跑最大费用最大流,得出来的是
当花费最多喜爱的糖果数时,造成的最大费用
此 时 的 最 大 流 m a x f l o w 是 使 用 的 喜 爱 糖 果 的 最 大 数 目 , 很 明 显 这 个 数 应 该 最 大 ( 因 为 不 用 价 值 就 是 1 ) 此时的最大流maxflow是使用的喜爱糖果的最大数目,很明显这个数应该最大(因为不用价值就是1) 此时的最大流maxflow是使用的喜爱糖果的最大数目,很明显这个数应该最大(因为不用价值就是1)
此 时 的 最 大 费 用 m a x c o s t 是 在 最 大 流 的 基 础 的 出 来 的 , 所 以 必 然 最 优 秀 此时的最大费用maxcost是在最大流的基础的出来的,所以必然最优秀 此时的最大费用maxcost是在最大流的基础的出来的,所以必然最优秀
那 么 还 剩 n − m a x f l o w 个 糖 果 , 每 颗 可 以 贡 献 1 个 价 值 那么还剩n-maxflow个糖果,每颗可以贡献1个价值 那么还剩n−maxflow个糖果,每颗可以贡献1个价值
设 b i 的 总 和 是 s u m n , 那 么 还 有 s u m n − m a x c o s t 个 单 位 价 值 需 要 被 填 充 设b_i的总和是sumn,那么还有sumn-maxcost个单位价值需要被填充 设bi的总和是sumn,那么还有sumn−maxcost个单位价值需要被填充
所 以 判 断 下 n − m a x f l o w > = s u m n − m a x c o s t 即 可 所以判断下n-maxflow>=sumn-maxcost即可 所以判断下n−maxflow>=sumn−maxcost即可
代码是用的最小费用最大流
#include <bits/stdc++.h>
using namespace std;
const int maxn=2e5+10;
const int inf=1e9;
int n,m,s,t,k;
int maxflow,mincost,b[maxn];
int dis[maxn],head[maxn<<1],cnt=1,incf[maxn],pre[maxn],vis[maxn];
struct edge{
int to,nxt,flow,w;//分别代表
}d[maxn<<1];
void add(int u,int v,int flow,int 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 )
{
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,i ;//倒回去找路径
maxflow+=incf[t],mincost+=dis[t]*incf[t];
while(x != s)
{
i=pre[x];
d[i].flow-=incf[t],d[i^1].flow+=incf[t];//加上流量
x = d[i^1].to;//因为是倒回去,所以利用反向边倒回去
}
}
}
int main()
{
int T; cin >> T;
int casenum=0;
while( T-- )
{
cin >> n >> m >> k;
s=0,t=n+m+1;
mincost=maxflow=0;
int sumn=0;
for(int i=1;i<=m;i++)
{
cin >> b[i];
sumn+=b[i];
add(i+n,t,b[i]/k,-k);
if( b[i]%k==0 ) continue;
add(i+n,t,1,-b[i]%k );
}
for(int i=1;i<=n;i++) add(s,i,1,0);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
int x; cin >> x;
if( x ) add(j,i+n,1,0);
}
dinic();
printf("Case #%d: ",++casenum);
if( n-maxflow>=sumn+mincost ) printf("YES\n");
else printf("NO\n");
cnt=1;
for(int i=s;i<=t;i++) head[i]=0;
}
}