先说PPT的思路
PPT的思路源于这句话:
对每条边 (u, v),连一条 (u, v) 容量为 1,费用为 1 的边。如果
流了表示删去这条边。
流过原图上的边表示删去这条边意味着什么呢?
令dif[u]=u的出度-入度
如图,灰边表示原图上的边,初始状态没有流过任何边
因为原图没有边被删,所以dif[u]=-1
这时,如果有一流量为1的流流过边a,那么此流只能从u在原图上的出边流出,即有一流量为1的流流过了边2,代表原图中边2被删,dif[u]=dif[u]-1=-2
因此,s到u流一流量为Δx的流,dif[u]要-Δx
同理,如果有一流量为1的流流过边b,那么此流只能从u在原图上的入边流入,即有一流量为1的流流过了边1或边3,代表原图中边1或边3被删,dif[u]=dif[u]+1=0
因此,u到t流一流量为Δx的流,dif[u]要+Δx
这样,我们就将dif[u]值的变化量,转化成了流过(s,u)边或(u,t)边的流量
如此,限制入度与出度的差的绝对值的最大值就变得简单了
因此考虑二分答案
设v=入度与出度的差的绝对值的最大值,二分v,然后求最少需要删去多少的边,判断是否可行即可
在建图时,
1.若dif[u]>=v:
则dif[u]要减去一个数x(x>=0),
因为-v<=dif[u]-x<=v,所以dif[u]-v<=x<=dif[u]+v,
从s到u连一条上界为dif[u]+v,下界为dif[u]-v,费用为0的边
2.若-v<dif[u]<v:
从s到u连一条上界为dif[u]+v,费用为0的边
从u到t连一条上界为v-dif[u],费用为0的边
3.若dif[u]<=-v:
从u到t连一条上界为v-dif[u],下界为-v-dif[u],费用为0的边
跑有源汇有上下界的最小费用最大流即可
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int N=60;
const int M=100005;
const int inf=0x7fffffff;
struct Edge{
int u,v,f,w,nxt;
}edge[M<<1];
int s,t,ss,tt,S,T,head[N],cnt,inque[N],pre[N],dis[N];
queue<int> que;
int Q,n,m,K,a[M],b[M],dif[N];
void add(int u,int v,int f,int w){
edge[cnt].u=u;edge[cnt].v=v;edge[cnt].w=w;edge[cnt].f=f;edge[cnt].nxt=head[u];head[u]=cnt++;
edge[cnt].u=v;edge[cnt].v=u;edge[cnt].w=-w;edge[cnt].f=0;edge[cnt].nxt=head[v];head[v]=cnt++;
}
bool spfa(){
memset(dis,0x7f,sizeof(dis));
memset(inque,0,sizeof(inque));
memset(pre,-1,sizeof(pre));
dis[S]=0;
que.push(S);inque[S]=1;
while(!que.empty()){
int u=que.front();
que.pop();inque[u]=0;
for(int i=head[u];i!=-1;i=edge[i].nxt){
int v=edge[i].v;
if(edge[i].f>0&&dis[v]>dis[u]+edge[i].w){
dis[v]=dis[u]+edge[i].w;
pre[v]=i;
if(!inque[v]){
que.push(v);inque[v]=1;
}
}
}
}
if(pre[T]==-1) return 0;
return 1;
}
int EK(){
int flow,ret=0;
while(spfa()){
flow=inf;
int x=pre[T];
while(x!=-1){
flow=min(edge[x].f,flow);
x=pre[edge[x].u];
}
x=pre[T];
while(x!=-1){
edge[x].f-=flow;
edge[x^1].f+=flow;
ret+=flow*edge[x].w;
x=pre[edge[x].u];
}
}
return ret;
}
bool check(int v){
cnt=0;memset(head,-1,sizeof(head));
s=n+1;t=n+2;ss=n+3;tt=n+4;
add(t,s,inf,0);
for(int i=1;i<=m;i++)
add(a[i],b[i],1,1);
for(int i=1;i<=n;i++){
if(dif[i]>=v){
add(s,i,2*v,0);
add(ss,i,dif[i]-v,0);
add(s,tt,dif[i]-v,0);
//add(s,i,[dif[i]+v,dif[i]-v],0);//减法上下界
}
else if(dif[i]>-v){
add(s,i,dif[i]+v,0);//减法上界
add(i,t,v-dif[i],0);//加法上界
}
else{
add(i,t,2*v,0);
add(ss,t,-v-dif[i],0);
add(i,tt,-v-dif[i],0);
//add(i,t,[v-dif[i],-v-dif[i]],0);//加法上下界
}
}
S=ss,T=tt;
if(EK()<=K) return 1;
return 0;
}
int main(){
scanf("%d",&Q);
for(int cas=1;cas<=Q;cas++){
memset(dif,0,sizeof(dif));
scanf("%d%d%d",&n,&m,&K);K=m-K;
for(int i=1;i<=m;i++){
scanf("%d%d",&a[i],&b[i]);
dif[b[i]]--;dif[a[i]]++;
}
int l=0,r=n,mid,ans;
while(l<=r){
mid=(l+r)/2;
if(check(mid)){
ans=mid;r=mid-1;
}
else l=mid+1;
}
printf("Case %d: %d\n",cas,ans);
}
return 0;
}
接下来是我自己的思考产物,还未验证
考虑如何转化 入度与出度之差。
设原图每条边流量为1,入度与出度之差转化为一个点的流入量与流出量之差。
此时图是不满足流量平衡的,所以对每个点,我们给少的流一个来处,多的流一个去处,
入度与出度之差就转化为从来处到节点的流(或从节点到去处的流)的大小。
顺着想下去,原图上的边有流流过,就代表选择了这条边,没有流流过,就代表边被删除
考虑如何求解
原题:最多k条边是限制,入度与出度之差的绝对值的最大值最小是求最值
考虑二分答案,入度与出度之差的绝对值变为限制(用限制流量实现),那么删去边数自然转为求的最值(用费用求),与d比较判断即可
update:
我的思路会产生一个问题,从源点到各节点的流的大小 代表 出度-入度, 从各节点到汇点的流的大小 代表 入度-出度,根据每个节点是出度大还是入度大,我们选择连(s,u)还是(u,t)(显然一个节点不能同时连两种边),但因为题目限制的是 出度与入度之差的绝对值,无论连哪种边,其下界都会是负数,目前我想到的唯一解决方法是将所有边的边界整体加上n