前言
网络流在解题思想与特性中,十分类似于动态规划,二者模板少,网络流难在构造图,动态规划难在构造转移方程,一旦构造正确,一切便迎刃而解
最大流模板
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2,n,m,dep[101010],st,ed;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>Q;
bool bfs()
{
memset(dep,0,sizeof(dep));
Q.push(st);
dep[st]=1;
while(!Q.empty())
{
int u=Q.front();
Q.pop();
for(int i=f[u]; i!=-1; i=nex[i])
{
int v=s[i].e;
if(dep[v]==0&&s[i].val)
{
dep[v]=dep[u]+1;
Q.push(v);
}
}
}
return dep[ed];
}
ll dfs(int now,ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].val)
{
ll temp=dfs(j,min(flow,s[x].val));
s[x].val-=temp;
s[x^1].val+=temp;
flow-=temp;
out+=temp;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now] = 0;
return out;
}
int main()
{
memset(f,-1,sizeof(f));
cin>>n>>m>>st>>ed;
for(int i=1; i<=m; i++)
{
int x,y;
ll z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,0);
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
cout<<ans;
return 0;
}
费用流模板
# include<iostream>
# include<cstring>
# include<queue>
using namespace std;
int n,m,st,ed,len=2;
typedef long long int ll;
typedef struct
{
int b,e;
ll flow,dis;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010], pre[101010],vis[101010];
ll dis[101010],minn[101010],mincost, maxflow;
queue<int>q;
void add(int x,int y,ll flow,ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
}
ll inf=1e18;
bool SPFA()
{
for(int i=1; i<=n; i++)
{
vis[i]=0;
dis[i]=inf;
}
q.push(st);
minn[st]=inf;
dis[st]=0;
vis[st]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow==0)
{
x=nex[x];
continue;
}
if(dis[j]>dis[now]+s[x].dis)
{
dis[j]=dis[now]+s[x].dis;
minn[j]=min(minn[now],s[x].flow);
pre[j]=x;
if(!vis[j])
{
vis[j]=1;
q.push(j);
}
}
x=nex[x];
}
}
return (dis[ed]!=inf);
}
void work()
{
while(SPFA())
{
int x=ed;
maxflow+=minn[ed];
mincost+=minn[ed]*dis[ed];
int i;
while(x!=st)
{
i=pre[x];
s[i].flow-=minn[ed];
s[i^1].flow+=minn[ed];
x=s[i^1].e;
}
}
}
int main ()
{
memset(f,-1,sizeof(f));
cin>>n>>m>>st>>ed;
for(int i=1; i<=m; i++)
{
int x,y;
ll flow,diss;
cin>>x>>y>>flow>>diss;
add(x,y,flow,diss);
add(y,x,0,-diss);
}
work();
cout<<maxflow<<" "<<mincost<<endl;
return 0;
}
最大流
经典样例 最大流模型题
题目英文解释非常繁琐,大致意思是 给m个点 ,np个发电站 ,nc个用户,n条边
np个发电站 给出每个发电站提供的最大流量
nc个用户,表示每个用户能接受的最大流量
n个边给出了发电站与用户之间的连接关系,以及最多能够输送的电量
求出某种方式连边下,用户获得的流量最大值
建图方式
一 超级源点向发电站连接发电站发电量的边
二 发电站向用户连接能够输送的电量
三 用户向汇点连接用户能够最多接受的上限
#include<iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
int n,n1,n2,m;
typedef struct
{
int b,e,flow;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,int z)
{
s[len].b=x;
s[len].e=y;
s[len].flow=z;
nex[len]=f[x];
f[x]=len;
len++;
}
int id1[101010],flow1[101010],id2[101010],flow2[101010];
int dep[101010];
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[0]=1;
q.push(0);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[n+1];
}
ll dfs(int now,ll flow)
{
if(now==n+1)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min((ll)s[x].flow,flow));
s[x].flow-=res;
s[x^1].flow+=res;
flow-=res;
out+=res;
}
if(flow==0)
break;
x=nex[x];
}
if(out==0)
dep[now]=0;
return out;
}
int main()
{
while(scanf("%d%d%d%d",&n,&n1,&n2,&m)!=EOF)
{
int x,y,z;
memset(f,-1,sizeof(f));
memset(nex,0,sizeof(nex));
len=2;
for(int i=1;i<=m;i++)
{
scanf(" (%d,%d)%d",&x,&y,&z);
x++;
y++;
add(x,y,z);
add(y,x,0);
}
for(int i=1;i<=n1;i++)
{
scanf(" (%d)%d",&x,&y);
x++;
id1[i]=x;
flow1[i]=y;
}
for(int i=1;i<=n2;i++)
{
scanf(" (%d)%d",&x,&y);
x++;
id2[i]=x;
flow2[i]=y;
}
for(int i=1;i<=n1;i++)
{
add(0,id1[i],flow1[i]);
add(id1[i],0,0);
}
for(int i=1;i<=n2;i++)
{
add(id2[i],n+1,flow2[i]);
add(n+1,id2[i],0);
}
int ans=0;
while(bfs())
{
ans+=dfs(0,1e9);
}
cout<<ans<<endl;
}
return 0;
}
最大流拆点与路径输出 例题二
最小路径覆盖就是用最少的路径数覆盖住全部点,一个点只能被一个路径覆盖一次,无法重复覆盖
是最大流的经典模型,结论为
最小路径覆盖数 =总点数-最大流
建图方式
一,每个点进行拆点操作,主点与超级源点相连,副点与超级汇点相连
二,存在一条有向边u-v时,u的主点,连接v的副点
三,每条边的流量都是1,每个点只能经过一次对应S到每个点的流量只有1,也就强制了每个点只能最多连接对面一个,同理,对面一个点也只能最多接受1个
流量意义
不难发现,一旦我们的超级汇点接受到1的流量,必定是新连接了两个点,左集点可能有指向它的点,但左集点一定没有指向的点,右集点对应的主点可能有指出的点,但该点一定没有指向它的点,即左集点代表了一个路径的结尾,右集点代表了一个路径的开头,一旦连接(流量+1),那么路径数必定减1,这样,初始时为n个路径,最终答案为 n-flow
残留网络对答案输出的意义
此类题目,残留网络中流量为0的边是我们重点关注的,一旦残流网络中有流量为0的边,也就意味着该边被流过一次,左集点指向了右集点,根据这一结论,设置前驱后继即可
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int n,m,st,ed,dep[101010],f[101010],nex[101010],len=2, pre[101010],last[101010];
queue<int>q;
typedef struct
{
int b,e;
ll flow;
ll init;
} xinxi;
xinxi s[101010];
void add(int x,int y,ll flow)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
nex[len]=f[x];
f[x]=len;
len++;
}
bool BFS()
{
for(int i=0; i<=2*n+1; i++)
{
dep[i]=0;
}
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow&&dep[j]==0)
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(s[x].flow,flow));
s[x].flow-=res;
s[x^1].flow+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
memset(f,-1,sizeof(f));
cin>>n>>m;
st=0;
ed=2*n+1;
for(int i=1; i<=m; i++)
{
int x,y;
cin>>x>>y;
add(x,y+n,1);
add(y+n,x,0);
}
for(int i=1; i<=n; i++)
{
add(0,i,1);
add(i,0,0);
}
for(int i=n+1; i<=n*2; i++)
{
add(i,ed,1);
add(ed,i,0);
}
ll ans=0;
while(BFS())
{
ans+=dfs(st,1e18);
}
for(int i=2; i<len; i++)
{
if(s[i].b>=1&&s[i].b<=n&&s[i].e>=n+1&&s[i].e<=2*n&&s[i].flow==0)
{
pre[s[i].e-n]=s[i].b;
last[s[i].b]=s[i].e-n;
}
}
for(int i=1; i<=n; i++)
{
if(!pre[i])
{
int now=i;
while(now)
{
cout<<now<<" ";
now=last[now];
}
cout<<endl;
}
}
cout<<n-ans<<endl;
return 0;
}
最大流拆点与路径输出 例题三
建图要点
一,球拆点,具有和为平方数的点进行左右集的有向边连接
二,动态加点,每次循环新加入一个球,并且补全超级源点超级汇点与该点的联系,补全之前左集点与该点的联系
三,仍旧是残量网络的前驱后继路径输出
流量与柱子数关系
这是前两个球放入的情况,可以看出,流量在两次操作之后一直是0,代表我们不得不开辟两个柱子来存储小球
当我们加入球3时,意外发现流量+1,这就意味着,我们原来的球1向球3连接了一条边并且成功流过去,也就是球3没有占据新的柱子,而是压在了球1
同理,在加入球4的时候没有流量获得,故新加柱子3,在放入球5的时候,获得流量加1
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int n,m,st,ed,f[101010],nex[101010],len=2,dep[10100],book[1000000+10],pre[101010],last[101010];
ll ans;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(s[x].val,flow));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(out==0)
dep[now]=0;
return out;
}
int main()
{
memset(f,-1,sizeof(f));
for(int i=1; i*i<=1000000; i++)
{
book[i*i]=1;
}
int nn,num=1,nown=0;
cin>>nn;
st=0;
ed=5000;
while(1)
{
add(st,num,1);
add(num,st,0);
add(num+ed,ed,1);
add(ed,num+ed,0);
for(int i=1; i<=num-1; i++)
{
if(book[i+num])
{
add(num,i+ed,1);
add(i+ed,num,0);
}
}
int temp=0;
while(bfs())
{
temp+=dfs(st,1e18);
}
if(temp==0)
nown++;
if(nown>nn)
{
break;
}
ans+=temp;
num++;
}
num--;
cout<<num<<endl;
for(int i=2; i<len; i++)
{
if(s[i].b>=1&&s[i].b<=num&&s[i].e>=num+1&&s[i].e<=num+ed&&s[i].val==0)
{
pre[s[i].e-ed]=s[i].b;
last[s[i].b]=s[i].e-ed;
}
}
for(int i=1; i<=num; i++)
{
if(!pre[i])
{
int now=i;
while(now)
{
cout<<now<<" ";
now=last[now];
}
cout<<endl;
}
}
return 0;
}
拆点控流 例题四
众所周知,网络流对于流量的控制体现于边权,而遇到严格控制点经过次数的模型,我们需要将点拆为正副点相连的边,边权为允许使用的次数
如图,点1会被经历无数次,而点2有了瓶颈限制,故只能经历1次
仅依靠边权控流的谬误性
下图中,蓝色点给予红色点各一点流量,红色点给予黄色点各一点流量,而红色点被流经两次,可见仅靠边权控流是不够的
建图要点
一,为了严格把控每个点的使用次数,我们必须把每个点都拆解成正副点
二,为了严格控制最长不下降子序列的长度,我们让超级原点连接dp[i]为1的点,dp[i]为最长不下降子序列长度的点连接超级汇点,其余各点,严格按照dp值相差1,位置前后关系,大小关系进行连边
三,我们超级汇点接受的流量,是由超级原点出发,途径大小严格递增,位置前后关系有序, dp值逐位加1的流量路径而得的,故其正确性可以保证
针对第三问,只需要在拆点而成的1,n两点,撤销流量限制,并且取消超级原点提供给位置1,超级汇点接受位置n的流量限制即可(在位置n能够连接超级汇点的前提下)
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef struct
{
int b,e;
ll val;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2,n,m, dep[101010];
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
dep[st]=1;
while(!q.empty())
q.pop();
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now,ll flow)
{
if(now==ed)
return flow;
int x=f[now];
ll out=0;
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(s[x].val,flow));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int a[1010],dp[1010];
int main()
{
memset(f,-1,sizeof(f));
cin>>n;
if(n==1)
{
cout<<1<<endl<<1<<endl<<1<<endl;
return 0;
}
for(int i=1;i<=n;i++)
{
cin>>a[i];
dp[i]=1;
}
st=0;
ed=5000;
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>=a[j])
dp[i]=max(dp[i],dp[j]+1);
}
}
int maxx=0;
for(int i=1;i<=n;i++)
{
maxx=max(maxx,dp[i]);
if(dp[i]==1)
{
add(st,i,1);
add(i,st,0);
}
}
for(int i=1;i<=n;i++)
{
if(dp[i]==maxx)
{
add(i+n,ed,1);
add(ed,i+n,0);
}
}
for(int i=1;i<=n;i++)
{
add(i,i+n,1);
add(i+n,i,0);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>=a[j]&&dp[i]==dp[j]+1)
{
//建立
add(j+n,i,1);
add(i,j+n,0);
}
}
}
cout<<maxx<<endl;
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
cout<<ans<<endl;
add(st,1,1e18);
add(1,1+n,1e18);
add(n,n+n,1e18);
if(dp[n]==maxx)
add(n,ed,1e18);
while(bfs())
{
ans+=dfs(st,1e18);
}
cout<<ans;
return 0;
}
拆点控流
例题五
与上一题不同的是,本题的路径是基于最短路的,故先筛选出来最短路径的边,进行连边即可
建图要点
一,超级原点与1号点之间是无穷权值,n号点与超级汇点之间也是无穷权值,二者不需要拆点,因为容量并无限制
二,具有容量限制的点必须拆点,边权即为容量限制
三,其余路径的流量统一设置为无穷,因为可以经过一个节点的次数是任意的,在拆点限流的情况下,其余路径的流量足够大即可
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll edge[1010][1010],inf=1e18,dis[1010],st,ed, n,m,dep[2020];
queue<int>q;
struct node
{
int id;
ll dis;
friend bool operator<(node a, node b)
{
return a.dis>b.dis;
}
};
priority_queue<node>qq;
bool book[1010];
void work()
{
for(int i=1; i<=n; i++)
{
dis[i]=inf;
}
dis[1]=0;
struct node head;
head.id=1;
head.dis=0;
qq.push(head);
while(!qq.empty())
{
struct node now=qq.top();
qq.pop();
if(book[now.id])
continue;
book[now.id]=1;
for(int i=1; i<=n; i++)
{
if(edge[now.id][i]!=inf)
{
if(dis[i]>dis[now.id]+edge[now.id][i])
{
dis[i]=dis[now.id]+edge[now.id][i];
struct node tail;
tail.dis=dis[i];
tail.id=i;
qq.push(tail);
}
}
}
}
}
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
bool bfs()
{
memset(dep,0,sizeof(dep));
dep[st]=1;
while(!q.empty())
q.pop();
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now,ll flow)
{
if(now==ed)
return flow;
int x=f[now];
ll out=0;
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(s[x].val,flow));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
ll val[101010];
int main()
{
memset(f,-1,sizeof(f));
cin>>n>>m;
st=0;
ed=2*n+1;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
edge[i][j]=inf;
}
}
for(int i=1; i<=m; i++)
{
int x,y;
ll z;
cin>>x>>y>>z;
edge[x][y]=min(edge[x][y],z);
edge[y][x]=min(edge[y][x],z);
}
work();
for(int i=1; i<=n; i++)
{
cin>>val[i];
}
add(st,1,inf);
add(1,st,0);
add(n+n,ed,inf);
add(ed,n+n,0);
for(int i=1; i<=n; i++)
{
if(i!=1&&i!=n)
{
add(i,i+n,val[i]);
add(i+n,i,0);
}
else
{
add(i,i+n,inf);
add(i+n,i,0);
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(dis[i]+edge[i][j]==dis[j])
{
add(i+n,j,inf);
add(j,i+n,0);
}
}
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
cout<<ans;
return 0;
}
拆点控流
仍旧是拆点,石柱就是点,L就是起始点,其高度就是容量,也就是能支持跳跃的次数,终点就是最终能够跳出去的点,注意inf的边,也就是我们控流之后无所谓的边,都设置成inf即可
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int n,m,d,st,ed,a[100][100],num=0,id[100][100],f[101010],nex[101010],len=2,dep[101010];
char mp[100][100];
ll inf=1e18;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now,ll flow)
{
if(now==ed)
return flow;
int x=f[now];
ll out=0;
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
st=0;
memset(f,-1,sizeof(f));
cin>>n>>m>>d;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
char ch;
cin>>ch;
a[i][j]=ch-'0';
if(a[i][j])
{
num++;
id[i][j]=num;
}
}
}
ed=num*2+3;
int cnt=0;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin>>mp[i][j];
if(mp[i][j]=='L')
{
add(st,id[i][j],1);
cnt++;
add(id[i][j],st,0);
}
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(a[i][j])
{
if(i-d<=0||i+d>n||j-d<=0||j+d>m)
{
add(id[i][j]+num,ed,inf);
add(ed,id[i][j]+num,0);
}
}
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(a[i][j])
{
add(id[i][j],id[i][j]+num,a[i][j]);
add(id[i][j]+num,id[i][j],0);
}
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(a[i][j])
{
for(int ii=1; ii<=n; ii++)
{
for(int jj=1; jj<=m; jj++)
{
if(i==ii&&j==jj)
continue;
if(d*d>=(ii-i)*(ii-i)+(jj-j)*(jj-j)&&a[ii][jj])
{
add(id[i][j]+num,id[ii][jj],inf);
add(id[ii][jj],id[i][j]+num,0);
}
}
}
}
}
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
cout<<cnt-ans;
return 0;
}
二分图的网络流解法
建图要点
一,一一对应的特性不仅体现在边权是1,而且是只有左右集,否则就会出现一配对多的情况
二,残流网络为0的边就是配对关系,输出即可
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2,pre[101010],last[101010],n,m,st,ed, dep[101010];
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
q.push(st);
dep[st]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
memset(f,-1,sizeof(f));
cin>>m>>n;
int x,y;
ed=n+1;
while(cin>>x>>y)
{
if(x==-1)
break;
add(x,y,1);
add(y,x,0);
}
for(int i=1; i<=m; i++)
{
add(st,i,1);
add(i,st,0);
}
for(int i=m+1; i<=n; i++)
{
add(i,ed,1);
add(ed,i,0);
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
cout<<ans<<endl;
for(int i=2;i<len;i++)
{
if(s[i].b>=1&&s[i].b<=m&&s[i].e>m&&s[i].e<=n&&s[i].val==0)
{
pre[s[i].e]=s[i].b;
last[s[i].b]=s[i].e;
}
}
for(int i=1;i<=m;i++)
{
if(pre[i]==0&&last[i])
{
cout<<i<<" "<<last[i]<<endl;
}
}
return 0;
}
网络流对于匹配问题相对于二分图的灵活性
建图要点
一,超级原点连接人员不仅为人员点提供了人数,更限制了每个人员所能流出的边数
二,每种人员对每个餐桌都连容量为1的边,保证了一个餐桌不会出现同种人员
三,餐桌对超级汇点连接容量是C的边,保证了餐桌不会超过容量
至于是否可行,只需判断S流出的是否被T全部接收即可,答案输出由残流网络确定即可
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int n,m, r[10010],c[10010],st,ed;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
int dep[101010];
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
q.push(st);
dep[st]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
vector<int>v[10010];
int main()
{
cin>>n>>m;
m+=n;
memset(f,-1,sizeof(f));
ed=m+1;
int sum=0;
for(int i=1; i<=n; i++)
{
cin>>r[i];
sum+=r[i];
add(st,i,r[i]);
add(i,st,0);
}
for(int i=1; i<=m-n; i++)
{
cin>>c[i];
add(i+n,ed,c[i]);
add(ed,i+n,0);
}
for(int i=1; i<=n; i++)
{
for(int j=n+1; j<=m; j++)
{
add(i,j,1);
add(j,i,0);
}
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
if(ans==sum)
{
cout<<1<<endl;
for(int i=2; i<len; i++)
{
if(s[i].val==0&&s[i].b>=1&&s[i].b<=n&&s[i].e>n&&s[i].e<=m)
{
v[s[i].b].push_back(s[i].e-n);
}
}
for(int i=1; i<=n; i++)
{
for(auto it:v[i])
{
cout<<it<<" ";
}
cout<<endl;
}
}
else
{
cout<<0;
}
return 0;
}
其他例题
圆环上任意相连两点之和都是质数,任意一点权值都是大于等于2的,这也就意味着,任意相邻两点 之和一定是奇数,考虑左集为偶数,右集为奇数
这样,我们便把模型转化为,一个偶数必须有两个奇数与之相连且和为质数,一个奇数必须有两个偶数与之相连且和为质数。
判断无解的情况时,显然偶数与奇数必须是数量相同的,另外,S流出的全部流量T要全部接入,这样才能保证每个偶数都有两个奇数与之相邻,任意奇数都有两个偶数与之相邻。
至于答案输出,在残流网络上寻找偶数到奇数且流量为0的边,进行双向连边操作,然后对于每一个点找环并标记即可,当前是奇数,那么下一个就是偶数,偶数同理,严格按照这种方式遍历,我们一定会标记出来一个合法的环
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
bool check(int x)
{
if(x==2)
return 1;
for(int i=2; i*i<=x; i++)
{
if(x%i==0)
return 0;
}
return 1;
}
int L[1010],R[1010],len1=0,len2=0, n,st,ed;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
int dep[101010];
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
vector<int>v[1010],work[1010];
int cnt=0,vis[101010], pre[1010],book1[1010],book2[1010];
void getans(int now,int type)
{
vis[now]=1;
work[cnt].push_back(now);
for(auto it:v[now])
{
if(vis[it]==0)
{
if(type==1)
{
if(book2[it])
{
getans(it,0);
}
}
else
{
if(book1[it])
{
getans(it,1);
}
}
}
}
}
int main()
{
cin>>n;
for(int i=1; i<=n; i++)
{
cin>>pre[i];
if(pre[i]&1)
{
len1++;
L[len1]=i;
book1[i]=1;
}
else
{
len2++;
R[len2]=i;
book2[i]=1;
}
}
if(len1!=len2)
{
cout<<"Impossible";
return 0;
}
st=0;
memset(f,-1,sizeof(f));
for(int i=1; i<=len1; i++)
{
add(st,L[i],2);
add(L[i],st,0);
}
ed=n+1;
for(int i=1; i<=len2; i++)
{
add(R[i],ed,2);
add(ed,R[i],0);
}
for(int i=1; i<=len1; i++)
{
for(int j=1; j<=len2; j++)
{
if(check(pre[L[i]]+pre[R[j]]))
{
add(L[i],R[j],1);
add(R[j],L[i],0);
}
}
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
if(ans!=len2*2)
{
cout<<"Impossible";
return 0;
}
memset(vis,0,sizeof(vis));
for(int i=2; i<len; i++)
{
if(s[i].val==0&&book1[s[i].b]&&book2[s[i].e])
{
v[s[i].b].push_back(s[i].e);
v[s[i].e].push_back(s[i].b);
}
}
for(int i=1; i<=len1; i++)
{
if(!vis[L[i]])
{
cnt++;
getans(L[i],1);
}
}
cout<<cnt<<endl;
for(int i=1; i<=cnt; i++)
{
cout<<work[i].size()<<" ";
for(auto it:work[i])
{
cout<<it<<" ";
}
cout<<endl;
}
return 0;
}
其他例题
每种食物或者饮料都只能被使用一次,每个牛只能享用一种食物和一种饮料,考虑把牛建立在饮料与食物之间,这样流过去之后,一个牛势必要和饮料和食物相连
值得注意的是,牛必须被拆点,否则就会出现下图上面牛的情况,而我们拆点之后就可以完美避开一个牛占有了多个食物与饮料的情况
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
//超级原点对饮料F进行连边
//内部奶牛正负链边
//副奶牛连接食物D
//食物对超级汇点进行连边
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
int n,m1,m2,st,ed, dep[101010];
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
q.push(st);
dep[st]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(!dep[j]&&s[x].val)
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].val)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
memset(f,-1,sizeof(f));
cin>>n>>m1>>m2;
//1-m1是饮料
//m1+1-m1+n是正点
//m1+1+n-m1+2n是副点
//m1+2n+1 -m1+2n+m2是食物
//m1+2n+m2+1是超级汇点
ed=m1+2*n+m2+1;
for(int i=1; i<=m1; i++)
{
add(st,i,1);
add(i,st,0);
}
for(int i=1; i<=m2; i++)
{
add(i+m1+2*n,ed,1);
add(ed,i+m1+2*n,0);
}
for(int i=1; i<=n; i++)
{
add(i+m1,i+m1+n,1);
add(i+m1+n,i+m1,0);
}
for(int i=1; i<=n; i++)
{
int cntf,cntd;
cin>>cntf>>cntd;
while(cntf--)
{
int x;
cin>>x;
add(x,i+m1,1);
add(i+m1,x,0);
}
while(cntd--)
{
int x;
cin>>x;
add(i+m1+n,x+m1+2*n,1);
add(x+m1+2*n,i+m1+n,0);
}
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
cout<<ans;
return 0;
}
分层图与网络流
地球初始与k人,那就先在time=0的时候超级原点给地球k的流量
之后,time每加1,我们需要完成顺承与转运操作,之后跑最大流,无需担心本次的顺承会对转运造成影响,因为为了增加最大流,本次一定会优先考虑从上一个状态转运来使得月球获得流量,其次才是顺承,本次的顺承对于增加本次最大流毫无意义,即顺承的结果是在考虑到转运最优之后的结果。
细节多,思维大,码量不少,还是比较有难度
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int fa[1010];
int n;
int st,ed,m,sum;
int getf(int x)
{
if(x==fa[x])
return x;
else
{
fa[x]=getf(fa[x]);
return fa[x];
}
}
int ren[1010];
vector<int>zhou[1010];
void hebing(int x,int y)
{
int t1=getf(x);
int t2=getf(y);
fa[t1]=t2;
}
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
ll inf=1e18;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
int dep[101010];
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
q.push(st);
dep[st]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now,ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].val)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
memset(f,-1,sizeof(f));
cin>>n>>m>>sum;
for(int i=0; i<=n+1; i++)
{
fa[i]=i;
}
ed=10100;
st=0;
for(int i=1; i<=m; i++)
{
cin>>ren[i];
int t;
cin>>t;
while(t--)
{
int x;
cin>>x;
if(x==-1)
x=n+1;
if(x==0)
x=n+2;
zhou[i].push_back(x);
}
}
int now=0;
ll ans=0;
while(now<=500)
{
if(now==0)
{
add(st,n+2+now*(n+2),sum);
add(n+2+now*(n+2),st,0);
}
add(n+1+now*(n+2),ed,inf);
add(ed,n+1+now*(n+2),0);
while(bfs())
{
ans+=dfs(st,1e18);
}
if(ans>=sum)
{
cout<<now;
return 0;
}
for(int i=1; i<=n+2; i++)
{
add(i+now*(n+2),i+(now+1)*(n+2),inf);
add(i+(now+1)*(n+2),i+now*(n+2),0);
}
now++;
for(int i=1; i<=m; i++)
{
int now1=now%zhou[i].size();
int pre1=(now1-1+zhou[i].size())%zhou[i].size();
now1=zhou[i][now1];
pre1=zhou[i][pre1];
add((n+2)*(now-1)+pre1,(n+2)*(now)+now1,ren[i]);
add((n+2)*(now)+now1,(n+2)*(now-1)+pre1,0);
}
}
cout<<0;
return 0;
}
并查集思想
网上的建图几乎是一模一样,省略掉了人,比较抽象,我这里建立了更好于理解的图,也就是猪圈与人共存的方式。注意,打开就是合并,之前打开了1,2,一万年之后打开2,3也是合并,123之间可以任意调度
建图要点
一, 第一次出现的猪开辟新的集合,出现在同一个客户,且是第一次出现的猪进行合并,如下图一号人打开了1,2猪圈,1,2猪圈第一次被打开,那么合并,并且合并权值
二,已经被打开的猪,通过之前的标记找到第一次出现的位置,并向人连边
三,inf表示各个猪圈,猪圈与人之间可以任意调度,limit与val已经严格保证了合理性,故无需在意
下图样例
一号人 1 2
二号人物 3
三号人物 1 2 4
#include<iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
ll inf=1e18;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
int book[1010], n,m,st,ed,val[1010],a[1010],last[1010],dep[1010];
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
cin>>m>>n;
memset(f,-1,sizeof(f));
for(int i=1; i<=m; i++)
{
cin>>a[i];
}
ed=n+1;
int cnt=ed;
for(int i=1; i<=n; i++)
{
int t;
cin>>t;
cnt++;
int flag=0;
while(t--)
{
int x;
cin>>x;
if(!book[x])
{
book[x]=1;
val[cnt]+=a[x];
last[x]=cnt;
flag=1;
}
else
{
add(last[x],i,inf);
add(i,last[x],0);
//last[x]=i;
}
}
ll z;
cin>>z;
if(flag)
{
add(cnt,i,inf);
add(i,cnt,0);
add(st,cnt,val[cnt]);
add(cnt,st,0);
}
add(i,ed,z);
add(ed,i,0);
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
//cout<<ans<<endl;
}
cout<<ans;
return 0;
}
其他例题
首先最可能想到如下建图方式
即针对经历次数设置流量,判断是否满流
但细究起来,是非常不正确的。最大流,一切以使得T获得最大流量为宗旨,一旦我们为了最大流,而使得原来正确的路线跑偏,便错误了
例如上图,完全有一种情况是,b1-a2 a1-b2能够跑出最大流,而这完全违背了题目要求
考虑下图构图,即将a1 a2交换位置,两次都能跑出最大流就能判断是否可能
#include<iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
int n,st1,ed1,st2,ed2,st,ed;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
int dep[1010];
ll inf=1e18;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
q.push(st);
dep[st]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now,ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
char ch[100][100];
int main()
{
int cnt1,cnt2;
while(cin>>n>>st1>>ed1>>cnt1>>st2>>ed2>>cnt2)
{
st1++;
ed1++;
st2++;
ed2++;
st=0;
ed=n+1;
len=2;
cnt1*=2;
cnt2*=2;
memset(f,-1,sizeof(f));
memset(nex,0,sizeof(nex));
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
cin>>ch[i][j];
if(ch[i][j]=='N')
{
add(i,j,inf);
add(j,i,0);
}
else if(ch[i][j]=='O')
{
add(i,j,2);
add(j,i,0);
}
}
}
add(st,st1,cnt1);
add(st1,st,0);
add(st,st2,cnt2);
add(st2,st,0);
add(ed2,ed,cnt2);
add(ed,ed2,0);
add(ed1,ed,cnt1);
add(ed,ed1,0);
ll ans1=0;
while(bfs())
{
ans1+=dfs(st,1e18);
}
if(ans1!=cnt1+cnt2)
{
cout<<"No"<<endl;
continue;
}
st=0;
ed=n+1;
len=2;
memset(f,-1,sizeof(f));
memset(nex,0,sizeof(nex));
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(ch[i][j]=='N')
{
add(i,j,inf);
add(j,i,0);
}
else if(ch[i][j]=='O')
{
add(i,j,2);
add(j,i,0);
}
}
}
swap(ed2,st2);
add(st,st1,cnt1);
add(st1,st,0);
add(st,st2,cnt2);
add(st2,st,0);
add(ed2,ed,cnt2);
add(ed,ed2,0);
add(ed1,ed,cnt1);
add(ed,ed1,0);
ll ans2=0;
while(bfs())
{
ans2+=dfs(st,1e18);
}
if(ans1==ans2)
{
cout<<"Yes"<<endl;
}
else
{
cout<<"No"<<endl;
}
}
return 0;
}
黑白染色
黑白染色之后,分成左右集,黑点改变,可以向上下左右四个方向的白点进行增加
当点数为偶数个的时候,黑点与白点个数相同,二分答案求解,得出的最大值的最小值,答案为左集或者右集流量(流满是前提)
当点数为奇数个的时候,黑点与白点个数不同,但一个黑点改变若干次必定有若干白点改变相同次数,即黑点与白点的改变次数是相同的,有以下方程
cnt白*max-sum 白= cnt黑*max-sum 黑
(cnt白-cnt黑)max=sum白-sum黑
max= (sum白-sum黑 )/(cnt白-cnt黑)
最终答案就是方程左边或者右边的值
#include<iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
ll a[100][100],ji,ou,sumji,sumou,cnt,st,ed,inf=1e18;
int n,m;
int id[100][100];
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[10000];
int f[10000],nex[10000],len=2;
int nx[4]= {0,0,-1,1};
int ny[4]= {1,-1,0,0};
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
int dep[2020];
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].val)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(flow==0)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
bool check(ll x)
{
len=2;
memset(f,-1,sizeof(f));
memset(nex,0,sizeof(nex));
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if(x<a[i][j])
return 0;
if((i+j)%2)
{
add(st,id[i][j],x-a[i][j]);
add(id[i][j],st,0);
}
else
{
add(id[i][j],ed,x-a[i][j]);
add(ed,id[i][j],0);
}
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
if((i+j)%2)
{
for(int k=0; k<4; k++)
{
int xx=i+nx[k];
int yy=j+ny[k];
if(xx<1||xx>n||yy<1||yy>m)
continue;
add(id[i][j],id[xx][yy],inf);
add(id[xx][yy],id[i][j],0);
}
}
}
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
return ans==x*ou-sumou;
}
int main()
{
//先对每个点进行序号标记
//再统计白色点,黑色点的个数,和
//求出x
//如果是奇数,那么对这个x进行检测
//建边白色点建立超级原点到该节点的mx-a[i][j]
//否则建立 到超级汇点的
//随后是,白色点对黑色点的单向边
//最后进行检测,要求出完全等于入
int t;
cin>>t;
while(t--)
{
cin>>n>>m;
ji=0;
ou=0;
sumji=0;
sumou=0;
cnt=0;
st=0;
ed=n*m+1;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cnt++;
id[i][j]=cnt;
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin>>a[i][j];
if((i+j)%2)
{
ji++;
sumji+=a[i][j];
}
else
{
ou++;
sumou+=a[i][j];
}
}
}
if((n*m)%2)
{
ll x=(sumji-sumou)/(ji-ou);
if((sumji-sumou)%(ji-ou)||!check(x))
{
cout<<-1<<endl;
}
else
{
cout<<x*ou-sumou<<endl;
}
}
else
{
ll l=1,r=2e9;
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
l=max(l,a[i][j]);
}
}
while(l<=r)
{
ll mid=(l+r)>>1;
if(check(mid))
r=mid-1;
else
l=mid+1;
}
cout<<l*ji-sumji<<endl;
}
}
return 0;
}
最小割问题
所谓最小割,是针对左右集与S,T的分属关系,将左右集全部点分割成两个集合,使得全部点分属于两个集合,两个集合不连通的最小代价,最小割等于最大流
如下图,可以通过割取val1来实现1点脱离S集合,划归T集合,也可以通过val2,val3的割取来实现2,3点脱离T集合,划归S集合
例题
本题对方格进行黑白染色,按照上图进行连边,考虑上图,1,2,3点在本题中的实际意义,最终流到T的流量是val1与val2+val3的最小值,这也就是对边进行了割取,割取之和,1,2,3被划归到同一集合,我们消耗了min(val1,val2+val3)的代价,同一集合是没有边相连的,也就是我们删去了一种最小耗费的情况,体现了最小割的本质。即超级汇点一旦接受了val的流量,势必是一个点与其冲突的点之间做了取舍,完成了一次最小割
#include<iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
ll a[100][100],ji,ou,sumji,sumou,cnt,st,ed,inf=1e18;
int n,m;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[10000];
int f[10000],nex[10000],len=2;
int nx[4]= {0,0,-1,1};
int ny[4]= {1,-1,0,0};
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
int dep[2020];
queue<int>q;
int id[110][110];
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].val)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(flow==0)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
int n,m;
cin>>n>>m;
memset(f,-1,sizeof(f));
ll ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>a[i][j];
id[i][j]=(i-1)*m+j;
ans+=a[i][j];
}
}
st=0;
ed=n*m+1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if((i+j)%2)
{
add(st,id[i][j],a[i][j]);
add(id[i][j],st,0);
if(id[i-1][j])
{
add(id[i][j],id[i-1][j],inf);
add(id[i-1][j],id[i][j],0);
}
if(id[i+1][j])
{
add(id[i][j],id[i+1][j],inf);
add(id[i+1][j],id[i][j],0);
}
if(id[i][j-1])
{
add(id[i][j],id[i][j-1],inf);
add(id[i][j-1],id[i][j],0);
}
if(id[i][j+1])
{
add(id[i][j],id[i][j+1],inf);
add(id[i][j+1],id[i][j],0);
}
}
else
{
add(id[i][j],ed,a[i][j]);
add(ed,id[i][j],0);
}
}
}
while(bfs())
{
ans-=dfs(st,inf);
}
cout<<ans;
return 0;
}
最小割
仍旧是类似套路,所谓选择的点数两两之和不是质数,且长度最长,其实就是删除最少的点,使得剩下的是两两没有边相连的,只不过这里我们每个点的代价是1。跑一个最大流,求出来最小割就OK了。值得注意的是,偶数集完全可以做到两两之间合不是质数,但是奇数集里面的1与1是无法做到的,而我们注意到其实1只需要选择一个就行了,故可以对原数据进行修改,全部的1当成一个1就行
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
bool not_prime[200000+10];
int prime[200000+10],tot;
void init()
{
for(int i=2; i<=200000; i++)
{
if(!not_prime[i])
{
tot++;
prime[tot]=i;
}
for(int j=1; j<=tot&&i*prime[j]<=200000; j++)
{
not_prime[i*prime[j]]=1;
if(i%prime[j]==0)
break;
}
}
}
int st,ed,n;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[5010100];
int f[5010100],nex[5010100],len=2;
ll inf=1e18;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
int dep[5000];
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now,ll flow)
{
if(ed==now)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(flow==0)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int L[5000],R[5000],a[5000];
int main()
{
init();
memset(f,-1,sizeof(f));
int flag=0;
int len1=0,len2=0;
cin>>n;
ed=n+1;
int cnt=0;
for(int i=1; i<=n; i++)
{
int x;
cin>>x;
a[i]=x;
if(x==1&&flag==0)
{
flag=1;
len1++;
L[len1]=i;
cnt++;
continue;
}
else if(x==1)
{
cnt++;
continue;
}
if(x%2==1)
{
len1++;
L[len1]=i;
}
else
{
len2++;
R[len2]=i;
}
}
for(int i=1; i<=len1; i++)
{
add(st,L[i],1);
add(L[i],st,0);
}
for(int i=1; i<=len2; i++)
{
add(R[i],ed,1);
add(ed,R[i],0);
}
for(int i=1; i<=len1; i++)
{
for(int j=1; j<=len2; j++)
{
if(!not_prime[a[L[i]]+a[R[j]]])
{
add(L[i],R[j],inf);
add(R[j],L[i],0);
}
}
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
if(cnt)
cout<<n-cnt+1-ans;
else
cout<<n-ans;
return 0;
}
集内连边的最小割
[SHOI2007] 善意的投票 / [JLOI2010] 冠军调查 - 洛谷
不难看出仍然是最小割的套路,左右两集分别进行超级源点与超级汇点的连边,按照是否同意午觉进行划分 。但本题的好朋友关系也存在与集内,即最小割也会存在通过割掉集内边来实现最小割的目的。特别注意朋友关系是双向的,我们平时建立的正副边虽然有两条,但确是单向作用,副边仅起到退流作用。
# include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
int n,m, st,ed, book[1000],dep[1000];
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[1010100];
int f[1010100],nex[1010100],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(flow==0)
break;
x=nex[x];
}
if(out==0)
dep[now]=0;
return out;
}
int main()
{
cin>>n>>m;
ed=n+1;
memset(f,-1,sizeof(f));
for(int i=1; i<=n; i++)
{
cin>>book[i];
if(book[i])
{
add(st,i,1);
add(i,st,0);
}
else
{
add(i,ed,1);
add(ed,i,0);
}
}
for(int i=1; i<=m; i++)
{
int x,y;
cin>>x>>y;
add(x,y,1);
add(y,x,0);
}
ll ans=0;
while(bfs())
{
ans=dfs(st,1e18);
}
cout<<ans;
return 0;
}
最大权闭合子图
最大权闭合子图是最小割问题中的变形,常用套路与数据特点是,事件具有因果,事件具有正负收益 。
连边方式为
超级源点连接正权收益点,超级汇点连接负权收益点,结果事件连接条件事件
正权集一般是结果,负权集一般是条件,图便有了实际意义,即耗费一定条件,获得一定收益结果
最大权闭合子图在最小割时取得最优解
# include <iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
int dep[101010*5];
queue<int>q;
int st,ed,n,m;
typedef struct
{
int b,e;
ll val;
}xinxi;
xinxi s[101010*5];
int f[101010*5],nex[101010*5],len=2;
ll inf=1e18;
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
ll val[101010];
int x[101010],y[101010],z[101010];
int main()
{
//建造不同的中转站耗费是同的,一旦建立
//具有该中转站的用户便可以进行通信,
//进行通信便可以获利
//最大权闭合图的构图方法
//增加超级源点S 汇点 T
//超级源点连接原图正权点,负权点连接汇点T
//正权点总和减去对应割的容量
//割最小时,闭合权最大
//首先,用户是贡献收益的,正权边连接源点
//编号是1-M个用户
//然后用户连接中转站,边权是INF,
//用户编号是M+1-M+N,
//超级源点是0,超级汇点是M+N+1
cin>>n>>m;
st=0;
ed=m+n+1;
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++)
{
cin>>val[i];
}
ll sum=0;
for(int i=1;i<=m;i++)
{
cin>>x[i]>>y[i]>>z[i];
sum+=z[i];
}
for(int i=1;i<=m;i++)
{
add(st,i,z[i]);
add(i,st,0);
}
for(int i=1;i<=m;i++)
{
add(i,x[i]+m,inf); //结果连接条件
add(x[i]+m,i,0);
add(i,y[i]+m,inf);
add(y[i]+m,i,0);
}
for(int i=1;i<=n;i++)
{
add(i+m,ed,val[i]);
add(ed,i+m,0);
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,1e18);
}
cout<<sum-ans<<endl;
return 0;
}
最大权闭合子图
NC23832 | Magic Slab |
仍旧是有代价,有收益,有条件(行列生成点),故为最大权闭合子图的典型。考虑把点作为左集点获得收益,另外特殊奖励点需要另外开辟点,只不过连接的右集点变多。
# include <iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
int n,m,st,ed;
int a[100][100];
int val1[100],val2[100];
int hang[100],lie[100];
typedef struct
{
int b,e;
ll val;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
ll inf=1e18;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
int dep[101010];
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].val)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
//左集点编号是1-n*m
//右集点是1-n 的行 n+1 - n+m的列
//超级汇点是 n*m+n+m+1
cin>>n>>m;
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++)
{
hang[i]=n*n+i;
}
for(int i=1;i<=n;i++)
{
lie[i]=n*n+n+i;
}
ed=n*n+n+n+m+1;
ll sum=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
cin>>a[i][j];
sum+=a[i][j];
}
}
for(int i=1;i<=n;i++)
{
cin>>val1[i];
}
for(int i=1;i<=n;i++)
{
cin>>val2[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
add(st,(i-1)*n+j,a[i][j]);
add((i-1)*n+j,st,0);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
add((i-1)*n+j,hang[i],inf);
add(hang[i],(i-1)*n+j,0);
add((i-1)*n+j,lie[j],inf);
add(lie[j],(i-1)*n+j,0);
}
}
for(int i=1;i<=n;i++)
{
add(hang[i],ed,val1[i]);
add(ed,hang[i],0);
}
for(int i=1;i<=n;i++)
{
add(lie[i],ed,val2[i]);
add(ed,lie[i],0);
}
for(int i=1;i<=m;i++)
{
int now=n*n+n+n+i;
int x1,x2,x3,x4;
int c;
cin>>x1>>x2>>x3>>x4>>c;
add(st,now,c);
add(now,st,0);
add(now,hang[x1],inf);
add(hang[x1],now,0);
add(now,lie[x2],inf);
add(lie[x2],now,0);
add(now,hang[x3],inf);
add(hang[x3],now,0);
add(now,lie[x4],inf);
add(lie[x4],now,0);
sum+=c;
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,inf);
}
cout<<sum-ans;
return 0;
}
例题
仍旧是最大权闭合子图的典型,点是条件,且本题是负权,边是结果,且本题是正权
# include <iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
int id[1010],n,m,val[1010],st,ed;
ll inf=1e18;
queue<int>q;
int dep[101010];
typedef struct
{
int b,e;
ll val;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(!dep[j]&&s[x].val)
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].val)
{
ll res=dfs(j,min(s[x].val,flow));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
int main()
{
memset(f,-1,sizeof(f));
//一条边的先决条件是两个点,而两个点是需要负权
//对m条边进行重新编号,对剩余n个点编号为m+i
cin>>n>>m;ed=n+m+1;
for(int i=1;i<=n;i++)
{
cin>>val[i];
id[i]=i+m;
add(id[i],ed,val[i]);
add(ed,id[i],0);
}
st=0;
ll sum=0;
for(int i=1;i<=m;i++)
{
int x,y,z;
cin>>x>>y>>z;
sum+=z;
add(st,i,z);
add(i,st,0);
x=id[x];
y=id[y];
add(i,x,inf);
add(x,i,0);
add(i,y,inf);
add(y,i,0);
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,inf);
}
cout<<sum-ans<<endl;
return 0;
}
最小割
本题考察最小割的本质,除了将左右集点完全划分为两个不相连边的集合之外,最小割的另一个本质是切掉最小的边,使得ST不再联通。
本题并没有明确的超级源点与汇点,且本题是要求删去点,而非边。本题也没有明确的左集(具有左右集时直接进行删边)故本题需要枚举超级源点与汇点,并且需要拆点,超级源点与汇点是不能删去的,其余的边也是不能删去的,超级源点与汇点之外的点即正副点之间的删除才是有意义的。注意这里的连通性指的是删除点之后的连通性,所以我们单纯删掉一个点的思路是不正确的。
# include <iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
//最小割树
int x[1000],y[1000];
int n,m,st,ed;
typedef struct
{
int b,e;
ll val;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
ll inf=1e18;
queue<int>q;
int dep[1010];
bool bfs()
{
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[st]=1;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(now==ed)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].val)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(flow==0)
break;
x=nex[x];
}
if(out==0)
dep[now]=0;
return out;
}
int main()
{
while(cin>>n>>m)
{
for(int i=1; i<=m; i++)
{
scanf(" (%d,%d)",&x[i],&y[i]);
}
ll ans=inf;
for(int i=0; i<n; i++)
{
for(int j=0; j<n; j++)
{
if(i==j)
continue;
st=i;
ed=j;
memset(f,-1,sizeof(f));
len=2;
memset(nex,0,sizeof(nex));
for(int k=0; k<n; k++)
{
if(k==st||k==ed)
{
add(k,k+n,inf);
add(k+n,k,0);
continue;
}
add(k,k+n,1);
add(k+n,k,0);
}
for(int k=1; k<=m; k++)
{
add(x[k]+n,y[k],inf);
add(y[k],x[k]+n,0);
add(y[k]+n,x[k],inf);
add(x[k],y[k]+n,0);
}
ll temp=0;
while(bfs())
{
temp+=dfs(st,inf);
}
ans=min(ans,temp);
}
}
if(n<=1||ans==inf)
cout<<n<<endl;
else
cout<<ans<<endl;
}
return 0;
}
最小割的 删除点操作
NC238090 | Anti LIS |
本题建图套路就是按照大小顺序,位置顺序,dp值递增顺序建图。而本题考察的最小割知识点在于最小割的第二个本质也就是ST之间不再联通。
首先观察,按照我们传统的建图方式跑出来的流量建立在ST联通的基础上,也就是路径长度为最长上升子序列长度。一旦我们在中间删除若干点(这里是拆点之后形成的边),ST联通路径上的全部路径都不再联通,那么我们就达成了降低最长上升子序列的目的,且跑最小割就是最小花费。
# include <iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
int dp[1010];
int a[1010];
int n,st,ed;
queue<int>q;
int dep[20200];
typedef struct
{
int b,e;
ll val;
}xinxi;
xinxi s[1010100];
int f[1010100],nex[1010100],len=2;
void add(int x,int y,ll z)
{
s[len].b=x;
s[len].e=y;
s[len].val=z;
nex[len]=f[x];
f[x]=len;
len++;
}
bool bfs()
{
memset(dep,0,sizeof(dep));
dep[st]=1;
while(!q.empty())
q.pop();
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[ed];
}
ll dfs(int now, ll flow)
{
if(ed==now)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].val&&dep[j]==dep[now]+1)
{
ll res=dfs(j,min(flow,s[x].val));
s[x].val-=res;
s[x^1].val+=res;
flow-=res;
out+=res;
}
if(flow==0)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
ll inf=1e18;
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n;
memset(f,-1,sizeof(f));
len=2;
memset(nex,0,sizeof(nex));
for(int i=1;i<=n;i++)
{
cin>>a[i];
dp[i]=1;
}
int maxx=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
dp[i]=max(dp[i],dp[j]+1);
}
maxx=max(maxx,dp[i]);
}
st=0;
ed=2*n+1;
for(int i=1;i<=n;i++)
{
if(dp[i]==1)
{
add(st,i,inf);
add(i,st,0);
}
else if(dp[i]==maxx)
{
add(i+n,ed,inf);
add(ed,i+n,0);
}
add(i,i+n,1);
add(i+n,i,0);
for(int j=1;j<i;j++)
{
if(a[i]>a[j]&&dp[i]==dp[j]+1)
{
add(j+n,i,inf);
add(i,j+n,0);
}
}
}
ll ans=0;
while(bfs())
{
ans+=dfs(st,inf);
}
cout<<ans<<endl;
}
return 0;
}
费用流
费用流的入门题
# include <iostream>
# include<queue>
# include<cstring>
using namespace std;
typedef long long int ll;
ll a[1010],b[1010],c[1010][1010],inf=1e18;
int n,m,st,ed;
typedef struct
{
int b,e;
ll flow,dis;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y, ll flow, ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
}
queue<int>q;
ll dis[1010],minn[1010],pre[1010];
bool book[1010];
bool SPFA()
{
for(int i=st; i<=ed; i++)
{
book[i]=0;
dis[i]=inf;
}
dis[st]=0;
book[st]=1;
q.push(st);
minn[st]=inf;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
book[now]=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow==0)
{
x=nex[x];
continue;
}
if(dis[j]>dis[now]+s[x].dis)
{
dis[j]=dis[now]+s[x].dis;
minn[j]=min(minn[now],s[x].flow);
pre[j]=x;
if(!book[j])
{
book[j]=1;
q.push(j);
}
}
x=nex[x];
}
}
return (dis[ed]!=inf);
}
ll maxflow,mindis;
void work()
{
while(SPFA())
{
int x=ed;
maxflow+=minn[ed];
mindis+=minn[ed]*dis[ed];
int i;
while(x!=st)
{
i=pre[x];
s[i].flow-=minn[ed];
s[i^1].flow+=minn[ed];
x=s[i^1].e;
}
}
}
int main()
{
memset(f,-1,sizeof(f));
cin>>n>>m;
for(int i=1; i<=n; i++)
{
cin>>a[i];
add(st,i,a[i],0);
add(i,st,0,0);
}
ed=n+m+1;
for(int i=1; i<=m; i++)
{
cin>>b[i];
add(i+n,ed,b[i],0);
add(ed,i+n,0,0);
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
cin>>c[i][j];
add(i,j+n,inf,c[i][j]);
add(j+n,i,0,-c[i][j]);
}
}
work();
cout<<mindis<<endl;
ed=n+m+1;
memset(f,-1,sizeof(f));
len=2;
mindis=0;
for(int i=1; i<=n; i++)
{
add(st,i,a[i],0);
add(i,st,0,0);
}
for(int i=1; i<=m; i++)
{
add(i+n,ed,b[i],0);
add(ed,i+n,0,0);
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
add(i,j+n,inf,-c[i][j]);
add(j+n,i,0,c[i][j]);
}
}
work();
cout<<-mindis<<endl;
return 0;
}
求从起点开始到终点再到起点的路径费用最小,起点与终点只经过两次,其余点经过一次,我们只需要拆点之后,起点与终点之间的边权设置为2,其余点设置为1即可。判断是否能够跑个来回,只需要判断流量是否是2.再此基础上,从起点跑一次残留网络到终点,再跑另一边即可。
# include <iostream>
# include<queue>
# include<cstring>
# include<map>
using namespace std;
typedef long long int ll;
int n,m,st,ed;
map<string,int>mp;
string city[1010];
typedef struct
{
int b,e;
ll flow,dis;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll flow,ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
}
ll pre[1010],dis[1010],book[1010],minn[1010],inf=1e18;
queue<int>q;
bool SPFA()
{
for(int i=st;i<=ed;i++)
{
book[i]=0;
dis[i]=-inf;
}
book[st]=1;
dis[st]=0;
minn[st]=inf;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
book[now]=0;
// cout<<now<<endl;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow==0)
{
x=nex[x];
continue;
}
if(dis[j]<dis[now]+s[x].dis)
{
dis[j]=dis[now]+s[x].dis;
pre[j]=x;
minn[j]=min(minn[now],s[x].flow);
if(book[j]==0)
{
book[j]=1;
q.push(j);
}
}
x=nex[x];
}
}
//cout<<dis[ed]<<endl;
return dis[ed]!=-inf;
}
ll maxcost,maxflow;
void work()
{
while(SPFA())
{
int x=ed;
maxflow+=minn[ed];
maxcost+=minn[ed]*dis[ed];
int i;
while(x!=st)
{
i=pre[x];
s[i].flow-=minn[ed];
s[i^1].flow+=minn[ed];
x=s[i^1].e;
}
}
}
bool vis[1010];
void dfs1(int now)
{
vis[now-n]=1;
cout<<city[now-n]<<endl;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
// cout<<j<<endl;
if(s[x].e>st&&s[x].e<=n&&vis[s[x].e]==0&&s[x].flow==0)
{
dfs1(s[x].e+n);
break;
}
x=nex[x];
}
}
void dfs2(int now)
{
vis[now-n]=1;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].e>st&&s[x].e<=n&vis[s[x].e]==0&&s[x].flow==0)
{
dfs2(s[x].e+n);
//break;
}
x=nex[x];
}
cout<<city[now-n]<<endl;
}
int main()
{
cin>>n>>m;
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++)
{
cin>>city[i];
mp[city[i]]=i;
}
st=0;
ed=2*n+1;
int flag=0;
for(int i=1;i<=m;i++)
{
string x,y;
cin>>x>>y;
int xx,yy;
xx=mp[x];
yy=mp[y];
if(xx>yy)
swap(xx,yy);
if(xx==1&&yy==n)
flag=1;
add(xx+n,yy,1,1);
add(yy,xx+n,0,-1);
}
for(int i=1;i<=n;i++)
{
if(i==1||i==n)
{
add(i,i+n,2,0);
add(i+n,i,0,0);
continue;
}
add(i,i+n,1,0);
add(i+n,i,0,0);
}
add(st,1,inf,0);
add(1,st,0,0);
add(2*n,ed,inf,0);
add(ed,2*n,0,0);
work();
if(maxflow==2)
{
cout<<maxcost<<endl;
dfs1(1+n);
dfs2(1+n);
}
else
{
if(maxflow==1)
{
if(flag)
{
cout<<2<<endl;
cout<<city[1]<<endl<<city[n]<<endl<<city[1]<<endl;
}
else
{
cout<<"No Solution!"<<endl;
}
}
else
{
cout<<"No Solution!"<<endl;
}
}
return 0;
}
仍旧是考察拆点,所谓路径不想交,也就是边与点都不相交,即拆点权为1,边也为1,拆点之后费用为权值。这是第一问的解法
第二问,不再拆点,上层点流向下层点时,边的费用改为上层点的权值即可
第三问,取消边的流量限制即可
值得注意的是,我们这三问,第一行的m个点全部只能经过一次,因为我们严格需要m条路径。
# include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll a[100][100];
typedef struct
{
int b,e;
ll flow,dis;
} xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void addd(int x,int y,ll flow, ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
}
void add(int x,int y,ll flow, ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
addd(y,x,0,-dis);
}
int pre[10100],vis[10100],st,ed;
ll minn[10100],dis[10100],inf=1e18;
queue<int>q;
bool SPFA()
{
for(int i=st; i<=ed; i++)
{
vis[i]=0;
dis[i]=-inf;
}
q.push(st);
minn[st]=inf;
dis[st]=0;
vis[st]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow==0)
{
x=nex[x];
continue;
}
if(dis[j]<dis[now]+s[x].dis)
{
dis[j]=dis[now]+s[x].dis;
minn[j]=min(minn[now],s[x].flow);
pre[j]=x;
if(!vis[j])
{
vis[j]=1;
q.push(j);
}
}
x=nex[x];
}
}
return (dis[ed]!=-inf);
}
ll ans=0;
void work()
{
ans=0;
while(SPFA())
{
int x=ed;
ans+=minn[ed]*dis[ed];
int i;
while(x!=st)
{
i=pre[x];
s[i].flow-=minn[ed];
s[i^1].flow+=minn[ed];
x=s[i^1].e;
}
}
}
void init()
{
len=2;
memset(f,-1,sizeof(f));
memset(nex,0,sizeof(nex));
st=0;
ed=10000;
}
int n,m,tot,id[100][100];
int main()
{
cin>>m>>n;
init();
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m+i-1; j++)
{
tot++;
id[i][j]=tot;
cin>>a[i][j];
}
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m+i-1; j++)
{
add(id[i][j],id[i][j]+tot,1,a[i][j]);
if(i<n)
{
add(id[i][j]+tot,id[i+1][j],1,0);
add(id[i][j]+tot,id[i+1][j+1],1,0);
}
}
}
for(int i=1; i<=m; i++)
{
add(st,id[1][i],1,0);
}
for(int i=1; i<=m+n-1; i++)
{
add(id[n][i]+tot,ed,1,0);
}
work();
cout<<ans<<endl;
init();
for(int i=1;i<=m;i++)
{
add(st,id[1][i],1,0);
}
for(int i=1;i<=m+n-1;i++)
{
add(id[n][i],ed,inf,a[n][i]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m+i-1;j++)
{
if(i==n)
continue;
add(id[i][j],id[i+1][j],1,a[i][j]);
add(id[i][j],id[i+1][j+1],1,a[i][j]);
}
}
work();
cout<<ans<<endl;
init();
for(int i=1;i<=m;i++)
{
add(st,id[1][i],1,0);
}
for(int i=1;i<=m+n-1;i++)
{
add(id[n][i],ed,inf,a[n][i]);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m+i-1;j++)
{
if(i==n)
continue;
add(id[i][j],id[i+1][j],inf,a[i][j]);
add(id[i][j],id[i+1][j+1],inf,a[i][j]);
}
}
work();
cout<<ans;
return 0;
}
比较独特的建图方式,离散化取点之后,为了使费用最大,我们最终是一定会走原线段。我们不相交的若干条路径,所耗费的流量仅仅为1,否则会多消耗一个流量。在最大流上限为k的前提下,我们最终会选取最多k路不相交线段,且费用最大
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int x[1000],y[1000],a[10100],tot;
int st,ed;
typedef struct
{
int b,e;
ll flow,dis;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll flow, ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
}
ll minn[1010],dis[1010],pre[1010];
bool vis[1010];
queue<int>q;
ll inf=1e18;
bool SPFA()
{
for(int i=0;i<=ed;i++)
{
vis[i]=0;
dis[i]=-inf;
}
vis[st]=1;
dis[st]=0;
minn[st]=inf;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow==0)
{
x=nex[x];
continue;
}
if(dis[j]<dis[now]+s[x].dis)
{
dis[j]=dis[now]+s[x].dis;
pre[j]=x;
minn[j]=min(minn[now],s[x].flow);
if(!vis[j])
{
vis[j]=1;
q.push(j);
}
}
x=nex[x];
}
}
return dis[ed]!=-inf;
}
ll ans;
void work()
{
while(SPFA())
{
ans+=minn[ed]*dis[ed];
int x=ed;
int i;
while(x!=st)
{
i=pre[x];
s[i].flow-=minn[ed];
s[i^1].flow+=minn[ed];
x=s[i^1].e;
}
}
}
int n,k;
int main()
{
cin>>n>>k;
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++)
{
cin>>x[i]>>y[i];
tot++;
a[tot]=x[i];
tot++;
a[tot]=y[i];
}
sort(a+1,a+1+tot);
int newlen=unique(a+1,a+1+tot)-a-1;
ed=newlen+1;
for(int i=1;i<=n;i++)
{
int now=y[i]-x[i];
x[i]=lower_bound(a+1,a+1+newlen,x[i])-a;
y[i]=lower_bound(a+1,a+1+newlen,y[i])-a;
add(x[i],y[i],1,now);
add(y[i],x[i],0,-now);
}
for(int i=1;i<newlen;i++)
{
add(i,i+1,inf,0);
add(i+1,i,0,0);
}
add(st,1,k,0);
add(1,st,0,0);
add(newlen,ed,k,0);
add(ed,newlen,0,0);
work();
cout<<ans;
return 0;
}
比较容易想到的建边思路,每个边只能采集一次也就是边建立两次,一次是一次性,有价值的,二次是无限次无价值的。难在下标与编号的分配,细节较多
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int st,ed;
typedef struct
{
int b,e;
ll flow,dis;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
ll inf=1e18;
void add(int x,int y,ll flow,ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
}
ll minn[101010],pre[101010],dis[101010];
queue<int>q;
bool vis[101010];
bool SPFA()
{
for(int i=st;i<=ed;i++)
{
dis[i]=-inf;
vis[i]=0;
}
vis[st]=1;
dis[st]=0;
minn[st]=inf;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow==0)
{
x=nex[x];
continue;
}
if(dis[j]<dis[now]+s[x].dis)
{
dis[j]=dis[now]+s[x].dis;
pre[j]=x;
minn[j]=min(minn[now],s[x].flow);
if(!vis[j])
{
vis[j]=1;
q.push(j);
}
}
x=nex[x];
}
}
return dis[ed]!=-inf;
}
ll ans;
void work()
{
while(SPFA())
{
ans+=minn[ed]*dis[ed];
int x=ed;
int i;
while(x!=st)
{
i=pre[x];
s[i].flow-=minn[ed];
s[i^1].flow+=minn[ed];
x=s[i^1].e;
}
}
}
int main()
{
int a,b;
cin>>a>>b;
int p,q;
cin>>p>>q;
memset(f,-1,sizeof(f));
for(int i=1;i<=p+1;i++)
{
for(int j=1;j<=q;j++)
{
int cost;
cin>>cost;
int l=(i-1)*(q+1)+j;
int r=(i-1)*(q+1)+j+1;
// cout<<l<<" "<<r<<" "<<"**"<<endl;
add(l,r,1,cost);
add(r,l,0,-cost);
add(l,r,inf,0);
add(r,l,0,0);
}
}
for(int j=1;j<=q+1;j++)
{
for(int i=1;i<=p;i++)
{
int cost;
cin>>cost;
int l=(i-1)*(q+1)+j;
int r=(i)*(q+1)+j;
// cout<<l<<" "<<r<<" "<<"**"<<endl;
add(l,r,1,cost);
add(r,l,0,-cost);
add(l,r,inf,0);
add(r,l,0,0);
}
}
st=0,ed=(p+2)*(q+2);
while(a--)
{
int k,x,y;
cin>>k>>x>>y;
int id=(q+1)*x+y+1;
add(st,id,k,0);
add(id,st,0,0);
}
while(b--)
{
int k,x,y;
cin>>k>>x>>y;
int id=(q+1)*x+y+1;
add(id,ed,k,0);
add(ed,id,0,0);
}
work();
cout<<ans;
return 0;
}
小于平均值的点需要接受流量,接受流量之后需要连接汇点,大于平均值的点需要提供流量,连接源点。每个相邻点之间可以交换,也就是费用为1.跑费用流即可
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll a[1000],sum;
int n,st,ed;
typedef struct
{
int b,e;
ll flow,dis;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll flow, ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
}
ll minn[1010],pre[1010],dis[1010],inf=1e18;
queue<int>q;
bool vis[1010];
bool SPFA()
{
for(int i=st;i<=ed;i++)
{
vis[i]=0;
dis[i]=inf;
}
dis[st]=0;
vis[st]=1;
minn[st]=inf;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
// cout<<now<<endl;
vis[now]=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow==0)
{
x=nex[x];
continue;
}
if(dis[j]>dis[now]+s[x].dis)
{
dis[j]=dis[now]+s[x].dis;
pre[j]=x;
minn[j]=min(minn[now],s[x].flow);
if(!vis[j])
{
vis[j]=1;
q.push(j);
}
}
x=nex[x];
}
}
// cout<<dis[ed]<<endl;
return dis[ed]!=inf;
}
ll ans;
void work()
{
while(SPFA())
{
// cout<<"*"<<endl;
int x=ed;
ans+=minn[ed]*dis[ed];
int i;
while(x!=st)
{
i=pre[x];
// cout<<x<<endl;
s[i].flow-=minn[ed];
s[i^1].flow+=minn[ed];
x=s[i^1].e;
}
}
}
int main()
{
memset(f,-1,sizeof(f));
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
sum+=a[i];
}
st=0;
ed=n+1;
sum/=n;
for(int i=1;i<=n;i++)
{
if(i==1)
{
add(i,n,inf,1);
add(n,i,0,-1);
add(i,i+1,inf,1);
add(i+1,i,0,-1);
}
else if(i==n)
{
add(i,i-1,inf,1);
add(i-1,i,0,-1);
add(i,1,inf,1);
add(1,i,0,-1);
}
else
{
add(i,i-1,inf,1);
add(i-1,i,0,-1);
add(i,i+1,inf,1);
add(i+1,i,0,-1);
}
}
for(int i=1;i<=n;i++)
{
if(a[i]<sum)
{
add(i,ed,sum-a[i],0);
add(ed,i,0,0);
}
else if(a[i]>sum)
{
add(st,i,a[i]-sum,0);
add(i,st,0,0);
}
}
work();
cout<<ans;
return 0;
}
餐巾纸的干净与否是需要拆点才能达成的,我们主点代表每日接受的干净纸巾,每日的干净纸巾用完之后由超级源点强制输送给脏纸巾,而不用干净纸巾和脏纸巾之间连边来完成。否则,我们每天的干净纸巾不一定真的就留给脏纸巾。我们每天产生的脏纸巾是一定的,是强制的
#include <bits/stdc++.h>
using namespace std;
typedef long long int ll;
int n;
int a[5000];
int st=0,ed;
typedef struct
{
int b,e;
ll flow,dis;
}xinxi;
xinxi s[1010100];
int f[1010100],nex[1010100],len=2;
void add(int x,int y,ll flow,ll dis)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
s[len].dis=dis;
nex[len]=f[x];
f[x]=len;
len++;
}
ll minn[1010010],dis[1010010],pre[1010100],inf=1e18;
bool vis[101010];
queue<int>q;
bool SPFA()
{
for(int i=st;i<=ed;i++)
{
vis[i]=0;
dis[i]=inf;
}
vis[st]=1;
dis[st]=0;
minn[st]=inf;
q.push(st);
while(!q.empty())
{
int now=q.front();
q.pop();
vis[now]=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow==0)
{
x=nex[x];
continue;
}
if(dis[j]>dis[now]+s[x].dis)
{
dis[j]=dis[now]+s[x].dis;
minn[j]=min(minn[now],s[x].flow);
pre[j]=x;
if(!vis[j])
{
vis[j]=1;
q.push(j);
}
}
x=nex[x];
}
}
// cout<<dis[ed]<<endl;
return dis[ed]!=inf;
}
ll ans;
void work()
{
while(SPFA())
{
ans+=minn[ed]*dis[ed];
int x=ed;
int i;
while(x!=st)
{
i=pre[x];
s[i].flow-=minn[ed];
s[i^1].flow+=minn[ed];
x=s[i^1].e;
}
}
}
int main()
{
cin>>n;
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int price,time1,price1,time2,price2;
cin>>price>>time1>>price1>>time2>>price2;
ed=n+n+1;
for(int i=1;i<=n;i++)
{
int zhu=i;
int fu=i+n;
add(zhu,ed,a[i],0);
add(ed,zhu,0,0);
add(st,zhu,inf,price);
add(zhu,st,0,-price);
add(st,fu,a[i],0);
add(fu,st,0,0);
if(i+time1<=n)
{
add(fu,i+time1,inf,price1);
add(i+time1,fu,0,-price1);
}
if(i+time2<=n)
{
add(fu,i+time2,inf,price2);
add(i+time2,fu,0,-price2);
}
if(i+1<=n)
{
add(fu,i+1+n,inf,0);
add(i+1+n,fu,0,0);
}
}
work();
cout<<ans;
return 0;
}
有上下界的网络流
每条路径的流量是有上下界的,我们需要强制把下限先流满的网络流。
构造方式
将每条边的上下界求出之后,原图上构造上界减去下界的边。
对于一条u-v的下界边 新建超级源点SS,汇点TT,u要流出,那么就与TT连接下界权值,v同理
连接原图的T-S 边权inf
新跑出的最大流应该是全部下界的和。
NC238100 | Budget |
本题中,按照上述方法建图验证之后,残留网络上剩余的流量(反向边上),加上下界即可
#include<bits/stdc++.h>
using namespace std;
typedef long long int ll;
ll up[500][500],down[500][500],inf=1e18;
ll a[500],b[500];
int n,m;
int S,T,SS,TT;
typedef struct
{
int b,e;
ll flow;
}xinxi;
xinxi s[101010];
int f[101010],nex[101010],len=2;
void add(int x,int y,ll flow)
{
s[len].b=x;
s[len].e=y;
s[len].flow=flow;
nex[len]=f[x];
f[x]=len;
len++;
}
int dep[101010];
queue<int>q;
bool BFS()
{
for(int i=0;i<=TT;i++)
{
dep[i]=0;
}
q.push(SS);
dep[SS]=1;
while(!q.empty())
{
int now=q.front();
q.pop();
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(s[x].flow&&!dep[j])
{
dep[j]=dep[now]+1;
q.push(j);
}
x=nex[x];
}
}
return dep[TT];
}
ll dfs(int now, ll flow)
{
if(now==TT)
return flow;
ll out=0;
int x=f[now];
while(x!=-1)
{
int j=s[x].e;
if(dep[j]==dep[now]+1&&s[x].flow)
{
ll temp=dfs(j,min(flow,s[x].flow));
s[x].flow-=temp;
s[x^1].flow+=temp;
flow-=temp;
out+=temp;
}
if(!flow)
break;
x=nex[x];
}
if(!out)
dep[now]=0;
return out;
}
void init(ll r,ll c,char ch, ll val)
{
if(ch=='>')
{
if(!r&&!c)
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
down[i][j]=max(down[i][j],val+1);
}
}
}
else if(!r)
{
for(int i=1; i<=n; i++)
{
down[i][c]=max(down[i][c],val+1);
}
}
else if(!c)
{
for(int j=1; j<=m; j++)
{
down[r][j]=max(down[r][j],val+1);
}
}
else
{
down[r][c]=max(down[r][c],val+1);
}
}
else if(ch=='<')
{
if(!r&&!c)
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
down[i][j]=max(down[i][j],0ll);
up[i][j]=min(up[i][j],val-1);
}
}
}
else if(!r)
{
for(int i=1; i<=n; i++)
{
down[i][c]=max(down[i][c],0ll);
up[i][c]=min(up[i][c],val-1);
}
}
else if(!c)
{
for(int j=1; j<=m; j++)
{
down[r][j]=max(down[r][j],0ll);
up[r][j]=min(up[r][j],val-1);
}
}
else
{
down[r][c]=max(down[r][c],0ll);
up[r][c]=min(up[r][c],val-1);
}
}
else
{
if(!r&&!c)
{
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
down[i][j]=val;
up[i][j]=val;
}
}
}
else if(!r)
{
for(int i=1; i<=n; i++)
{
down[i][c]=val;
up[i][c]=val;
}
}
else if(!c)
{
for(int j=1; j<=m; j++)
{
down[r][j]=val;
up[r][j]=val;
}
}
else
{
down[r][c]=val;
up[r][c]=val;
}
}
}
ll res[500][500];
int main()
{
cin>>n>>m;
memset(f,-1,sizeof(f));
for(int i=1; i<=n; i++)
{
for(int j=1; j<=m; j++)
{
up[i][j]=inf;
}
}
for(int i=1; i<=n; i++)
{
cin>>a[i];
}
for(int i=1; i<=m; i++)
{
cin>>b[i];
}
int t;
cin>>t;
while(t--)
{
ll r,c,val;
char ch;
cin>>r>>c>>ch>>val;
init(r,c,ch,val);
}
S=n+m+1;
T=n+m+2;
SS=n+m+3;
TT=n+m+4;
ll sum=0;
for(int i=1;i<=n;i++)
{
add(S,i,a[i]);
add(i,S,0);
sum+=a[i];
}
for(int i=1;i<=m;i++)
{
add(i+n,T,b[i]);
add(T,i+n,0);
sum+=b[i];
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
add(i,j+n,up[i][j]-down[i][j]);
add(j+n,i,0);
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
add(i,TT,down[i][j]);
add(TT,i,0);
sum+=down[i][j];
add(SS,j+n,down[i][j]);
add(j+n,SS,0);
}
}
for(int i=1;i<=n;i++)
{
add(S,TT,a[i]);
add(TT,S,0);
add(SS,i,a[i]);
add(i,SS,0);
}
for(int i=1;i<=m;i++)
{
add(i+n,TT,b[i]);
add(TT,i+n,0);
add(SS,T,b[i]);
add(T,SS,0);
}
add(T,S,inf);
ll ans=0;
while(BFS())
{
ans+=dfs(SS,inf);
}
if(ans!=sum)
{
cout<<"IMPOSSIBLE"<<endl;
return 0;
}
for(int i=1;i<=n;i++)
{
int x=f[i];
while(x!=-1)
{
int j=s[x].e;
if(j>=n+1&&j<=n+m)
{
res[i][j-n]=s[x^1].flow+down[i][j-n];
}
x=nex[x];
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cout<<res[i][j]<<" ";
}
cout<<endl;
}
return 0;
}