网络流24题:
from:Luogu
- P4011 孤岛营救问题
- P2756 飞行员配对方案问题
- P2761 软件补丁问题
- P4016 负载平衡问题
- P3358 最长k可重区间集问题
- P4014 分配问题
- P4009 汽车加油行驶问题
- P4015 运输问题
- P2774 方格取数问题
- P4012 深海机器人问题
- P2762 太空飞行计划问题
- P2770 航空路线问题
- P2754 [CTSC1999]家园 / 星际转移问题
- P3254 圆桌问题
- P1251 餐巾计划问题
- P2763 试题库问题
- P2766 最长不下降子序列问题
- P3355 骑士共存问题
- P3357 最长k可重线段集问题
- P4013 数字梯形问题
- P3356 火星探险问题
- P2765 魔术球问题
- P2764 最小路径覆盖问题
- P2775 机器人路径规划问题
0.前言
网络流24题总体来说基本难度不大(混进了一些奇怪的东西),因为网络流的模型实在太多了,像我这种无法举一反三的菜鸡只能靠多做来积累
网络流题目的核心也是难点在于怎么将问题进行模型转化从而建图,在本文中我会解释每一题的心路历程,建图方式,以及为什么要这样建图,(作为一篇网络流题解不解释建图方式等于啥也没说)
1.P4011 孤岛营救问题
首先,这不是一个网络流题目
你要问我为什么的话我只能说:老婆饼里面也没有老婆!
题意:有一些钥匙散布在一些地方,有些地方有障碍,有的地方有门,需要拥有对应的钥匙才能通过这条门,一个地方可能有多把钥匙
思路:因为要保存钥匙的状态,开数组不方便并且有空间压力,所以状压,0-1 BFS即可
状态设置为:
x,y :坐标
key:当前拥有的钥匙
dis:离原点的距离
#include<bits/stdc++.h>
using namespace std;
const int maxn=20;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
int e[maxn][maxn][maxn][maxn],key[maxn][maxn][maxn];
int cnt[maxn][maxn],vis[maxn][maxn][1<<15];
int n,m,k,p,s;
struct node{
int x,y,key,dis;
};
inline int getkey(int x,int y){
int ans=0;
for(int i=1;i<=cnt[x][y];i++){
ans|=(1<<(key[x][y][i]-1));
}
return ans;
}
inline int bfs(int sx,int sy){
int skey=getkey(sx,sy);
vis[sx][sy][skey]=1;
queue<node>q;
q.push((node){sx,sy,skey,0});
while(!q.empty()){
node u=q.front();
q.pop();
if(u.x==n&&u.y==m){
return u.dis;
}
int nowkey=u.key;
for(int i=0;i<4;i++){
int xx=u.x+dx[i];
int yy=u.y+dy[i];
int nexkey=getkey(xx,yy);
int dd=nowkey|nexkey;
if(e[u.x][u.y][xx][yy]==-1||xx<1||xx>n||yy<1||yy>m||(e[u.x][u.y][xx][yy]>0&&!(nowkey&(1<<(e[u.x][u.y][xx][yy]-1)))))continue;
if(!vis[xx][yy][dd]){
vis[xx][yy][dd]=1;
q.push((node){xx,yy,dd,u.dis+1});
}
}
}
return -1;
}
int main(){
cin>>n>>m>>p;
cin>>k;
for(int i=1;i<=k;i++){
int x1,y1,x2,y2,g;
cin>>x1>>y1>>x2>>y2>>g;
if(g){
e[x1][y1][x2][y2]=g;
e[x2][y2][x1][y1]=g;
}
else{
e[x1][y1][x2][y2]=-1;
e[x2][y2][x1][y1]=-1;
}
}
cin>>s;
for(int i=1;i<=s;i++){
int x,y,q;
cin>>x>>y>>q;
key[x][y][++cnt[x][y]]=q;
}
printf("%d",bfs(1,1));
return 0;
}
2.P2756 飞行员配对方案问题
输出配对方案即可
纯板子题
匈牙利完直接输出
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=2e3+5;
int n,m,k;
int e[maxn][maxn];
int match[maxn],vis[maxn];
bool dfs(int u){
for(int v=m+1;v<=n;v++){
if(vis[v]||!e[u][v])continue;
vis[v]=1;
if(!match[v]||dfs(match[v])){// 如果v没有匹配,或者v的匹配找到了新的匹配
match[v]=u;
match[u]=v; // 更新匹配信息
return true;
}
}
return false;
}
int xyl(){
int ans=0;
memset(match,0,sizeof(match));
for(int i=1;i<=n;i++){// 对每一个点尝试匹配
memset(vis,0,sizeof(vis));
if(dfs(i))ans++;
}
return ans;
}
inline void init(){
memset(e,0,sizeof(e));
}
int main(){
scanf("%d%d",&m,&n);
int u,v;
while(scanf("%d%d",&u,&v)){
if(u==-1&&v==-1)break;
e[u][v]=1;
e[v][u]=1;
}
printf("%d\n",xyl());
for(int i=1;i<=m;i++){
if(match[i]){
printf("%d %d\n",i,match[i]);
}
}
return 0;
}
3.P2761 软件补丁问题
题意:对于每一个补丁 i i i,都有 2 个与之相应的错误集合 B 1 [ i ] B1[i] B1[i]和 B 2 [ i ] B2[i] B2[i],使得仅当软件包含 B1[i]中的所有错误,而不包含 B 2 [ i ] B2[i] B2[i]中的任何错误时,才可以使用补丁 i i i。补丁 i i i 将修复软件中的某些错误 F 1 [ i ] F1[i] F1[i],而同时加入另一些错误 F 2 [ i ] F2[i] F2[i]。另外,每个补丁都耗费一定的时间。
思路:数据范围和状态表示暗示要状压,可是dp会有后效性,所以 s p f a spfa spfa,因为边权并非全相等,用普通 b f s bfs bfs的话不一定选到的是最优方式,重载优先队列的话可以达到同样效果
注意:如果暴力加上所有边的话边数会过多,直接在 s p f a spfa spfa的过程中进行状态转移
#include<bits/stdc++.h>
using namespace std;
const int maxn=25;
int b1[1<<22],b2[1<<22],f2[1<<22],f1[1<<22];
int t[105];
int n,m;
int inque[1<<22],dis[1<<22];
inline void spfa(int s){
memset(dis,0x3f,sizeof dis);
memset(inque,0,sizeof inque);
dis[s]=0;
queue<int>q;
q.push(s);
inque[s]=1;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=1;i<=m;i++){
if((u&b1[i])==b1[i]&&!(u&b2[i])){
int v=(u|f1[i])^f1[i]|f2[i];
if(dis[v]>dis[u]+t[i]){
dis[v]=dis[u]+t[i];
if(!inque[v]){
inque[v]=1;
q.push(v);
}
}
}
}
}
cout<<(dis[0]>=0x3f3f3f3f/2?0:dis[0]);
return;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
scanf("%d",&t[i]);
scanf(" ");
char x;
for(int j=1;j<=n;j++){
scanf("%c",&x);
if(x=='+')b1[i]|=(1<<j-1);
else if(x=='-')b2[i]|=(1<<j-1);
}
scanf(" ");
for(int j=1;j<=n;j++){
scanf("%c",&x);
if(x=='-')f1[i]|=(1<<j-1);
else if(x=='+')f2[i]|=(1<<j-1);
}
}
int st=(1<<n)-1;
spfa(st);
return 0;
}
4.P4016 负载平衡问题
题意: G G G公司有 n n n 个沿铁路运输线环形排列的仓库,每个仓库存储的货物数量不等。如何用最少搬运量可以使 n n n 个仓库的库存数量相同。搬运货物时,只能在相邻的仓库之间搬运。
思路:
1.源点到每个仓库连上货量的流量,费用为
0
0
0
2.仓库间两两连无限流量,费用为 1 1 1的边
3.仓库到汇点连上流量为总货物的平均值,费用为0的边
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf=0x3f3f3f3f,maxn=1005,maxm=100005;
struct edge{
int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m;
inline void add_edge(int u,int v,int cost,int flow){
e[++cnt].v=v;
e[cnt].cost=cost;
e[cnt].flow=flow;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].cost=-cost;
e[cnt].flow=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
int inque[maxn];
int dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
queue<int>q;
memset(dis,0x3f,sizeof dis);
memset(flow,0x3f,sizeof flow);
memset(inque,0,sizeof inque);
q.push(s);
inque[s]=1;
dis[s]=0;
pre[t]=-1;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!inque[v]){
q.push(v);
inque[v]=1;
}
}
}
}
if(pre[t]!=-1)return true;
return false;
}
int maxflow,mincost;
void mcmf(int s,int t){
maxflow=0;
mincost=0;
while(spfa(s,t)){
int u=t;
// printf("maxflow==%d %d %d\n",maxflow,flow[t],flow[t]*dis[t]);
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(u!=s){
e[last[u]].flow-=flow[t];
e[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
return;
}
signed main(){
cin>>n;
int s=n+1;
int t=n+2;
int x=0,sum=0;
for(int i=1;i<=n;i++){
cin>>x;
sum+=x;
add_edge(s,i,0,x);
if(i!=n){
add_edge(i,i+1,1,inf);
}
else{
add_edge(n,1,1,inf);
}
if(i!=1){
add_edge(i,i-1,1,inf);
}
else{
add_edge(1,n,1,inf);
}
}
int av=sum/n;
for(int i=1;i<=n;i++){
add_edge(i,t,0,av);
}
mcmf(s,t);
cout<<mincost;
return 0;
}
5.P3358 最长k可重区间集问题
6.P4014 分配问题
题意: n n n 件工作要分配给 n n n 个人做。第 i i i 个人做第 j j j 件工作产生的效益为 c i j c_{ij} cij 。试设计一个将 n n n 件工作分配给 n n n 个人做的分配方案,使产生的总效益最大。
思路:给每个人分配到最大价值的工作,二分图最大权匹配,裸 k m km km即可
#include<bits/stdc++.h>
using namespace std;
#define int long long
const long long maxn=1005,inf=1e18;
int n,m,mp[maxn][maxn],matched[maxn];
int slack[maxn],ex[maxn],ey[maxn],pre[maxn];
int visx[maxn],visy[maxn];
inline void init(){
memset(slack,0,sizeof slack);
memset(ex,0,sizeof ex);
memset(ey,0,sizeof ey);
memset(pre,0,sizeof pre);
memset(matched,0,sizeof matched);
memset(visx,0,sizeof visx);
memset(visy,0,sizeof visy);
}
void match(int u){
int x,y=0,yy=0,d;
memset(pre,0,sizeof(pre));
for(int i=1;i<=n;i++)slack[i]=inf;
matched[y]=u;
while(1){
x=matched[y];
d=inf;
visy[y]=1;
for(int i=1;i<=n;i++){
if(visy[i])continue;
if(slack[i]>ex[x]+ey[i]-mp[x][i]){
slack[i]=ex[x]+ey[i]-mp[x][i];
pre[i]=y;
}
if(slack[i]<d){
d=slack[i];
yy=i;
}
}
for(int i=0;i<=n;i++){
if(visy[i])ex[matched[i]]-=d,ey[i]+=d;
else slack[i]-=d;
}
y=yy;
if(matched[y]==-1)break;
}
while(y){
matched[y]=matched[pre[y]];
y=pre[y];
}
}
int km(){
memset(matched,-1,sizeof(matched));
memset(ex,0,sizeof(ex));
memset(ey,0,sizeof(ey));
for(int i=1;i<=n;i++){
memset(visy,0,sizeof(visy));
match(i);
}
int ans=0;
for(int i=1;i<=n;i++){
if(matched[i]!=-1)ans+=mp[matched[i]][i];
}
return ans;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
mp[i][j]=-inf;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%lld",&mp[i][j]);
}
}
int mm=km();
init();
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
mp[i][j]=-mp[i][j];
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(mp[i][j]==-inf)mp[i][j]=inf;
printf("%lld\n%lld",-km(),mm);
return 0;
}
7.P4009 汽车加油行驶问题
题意:如果油不满,遇到加油站必须加满油付出代价a,如果回头走需要付出代价b,可以在任意一个地方建加油站付出代价c(神仙?)
思路:分层图最短路,记录每个点的剩余油量,遇到加油站且油不满直接加油并 c o n t i n u e continue continue
#include<bits/stdc++.h>
using namespace std;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,1,-1};
int n,m,k,a,b,c;
int vis[105][105][12];
int mp[105][105];
int dis[105][105][12],inque[105][105][12];
struct node{
int x,y,res;
};
inline void spfa(int sx,int sy){
memset(dis,0x3f,sizeof dis);
dis[sx][sy][k]=0;
queue<node>q;
q.push((node){sx,sy,k});
inque[sx][sy][k]=1;
while(!q.empty()){
node u=q.front();
q.pop();
int x=u.x;
int y=u.y;
int res=u.res;
inque[x][y][res]=0;
if(mp[x][y]&&res!=k){
if(dis[x][y][k]>dis[x][y][res]+a){
dis[x][y][k]=dis[x][y][res]+a;
if(!inque[x][y][k]){
inque[x][y][k]=1;
q.push((node){x,y,k});
}
}
continue;
}
else{
if(dis[x][y][k]>dis[x][y][res]+a+c){
dis[x][y][k]=dis[x][y][res]+a+c;
if(!inque[x][y][k]){
inque[x][y][k]=1;
q.push((node){x,y,k});
}
}
}
if(res>0){
for(int i=0;i<4;i++){
int xx=x+dx[i];
int yy=y+dy[i];
int f=0;
if(xx<x||yy<y)f=b;
if(xx<1||yy<1||xx>n||yy>n)continue;
if(dis[xx][yy][res-1]>dis[x][y][res]+f){
dis[xx][yy][res-1]=dis[x][y][res]+f;
if(!inque[xx][yy][res-1]){
inque[xx][yy][res-1]=1;
q.push((node){xx,yy,res-1});
}
}
}
}
}
}
int main(){
cin>>n>>k>>a>>b>>c;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>mp[i][j];
}
}
spfa(1,1);
int mm=0x3f3f3f3f;
for(int i=0;i<=k;i++){
mm=min(mm,dis[n][n][i]);
}
cout<<mm;
return 0;
}
8.P4015 运输问题
题意:设计一个将仓库中所有货物运送到零售商店的运输方案,输出最小运输费用和最大运输费用
思路:二分图,分别跑一次最大最小费用流即可
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{
int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
e[++cnt].v=v;
e[cnt].cost=cost;
e[cnt].flow=flow;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].cost=-cost;
e[cnt].flow=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
queue<int>q;
memset(dis,0x3f,sizeof dis);
memset(flow,0x3f,sizeof flow);
memset(inque,0,sizeof inque);
q.push(s);
inque[s]=1;
dis[s]=0;
pre[t]=-1;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!inque[v]){
q.push(v);
inque[v]=1;
}
}
}
}
if(pre[t]!=-1)return true;
return false;
}
unordered_map<string,int>mp;
inline pair<int,int> mcmf(int s,int t){
int maxflow=0;
int mincost=0;
while(spfa(s,t)){
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(u!=s){
e[last[u]].flow-=flow[t];
e[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
return {maxflow,mincost};
}
inline int id(int i,int j){
return (i-1)*n+j;
}
int a[50005],b[50005],x[50005];
int main(){
cin>>m>>n;
int s=n+m+1;
int t=n+m+2;
for(int i=1;i<=m;i++){
scanf("%d",&a[i]);
add_edge(s,i,0,a[i]);
}
for(int i=1;i<=n;i++){
scanf("%d",&b[i]);
add_edge(i+m,t,0,b[i]);
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
scanf("%d",&x[id(i,j)]);
add_edge(i,j+m,x[id(i,j)],a[i]);
}
}
pii yaoyao=mcmf(s,t);
cout<<yaoyao.second<<endl;
cnt=1;
memset(head,0,sizeof head);
for(int i=1;i<=m;i++){
add_edge(s,i,0,a[i]);
}
for(int i=1;i<=n;i++){
add_edge(i+m,t,0,b[i]);
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
add_edge(i,j+m,-x[id(i,j)],a[i]);
}
}
pii yaoyao2=mcmf(s,t);
cout<<-yaoyao2.second<<endl;
return 0;
}
9.P2774 方格取数问题
题意:选出一些数,这些数不能相邻,怎么取能使所取的数总和最大
思路:
首先通过观察可以发现,相邻的格子是一定不能取的
先把整个图进行染色
选取一些点,逆向思维可以转化为一开始全选,然后舍弃一些点,于是想到最小割
源点向所有白点连边,黑点向所有汇点连边,相邻的黑白点连容量为 i n f inf inf的边
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};
int cnt=1,n,m,s,t;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
inline int id(int x,int y){
return (x-1)*m+y;
}
int f[maxn],se[maxn],sum;
signed main(){
scanf("%lld%lld",&n,&m);
int u,v,w;
s=0,t=n*m+1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
scanf("%lld",&f[id(i,j)]);
sum+=f[id(i,j)];
if((i+j)%2==1){
se[id(i,j)]=1;
add_edge(s,id(i,j),f[id(i,j)]);
}
else if((i+j)%2==0){
add_edge(id(i,j),t,f[id(i,j)]);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if((i+j)%2==1){
int dd=id(i,j);
int ff=f[dd];
for(int k=0;k<4;k++){
int xx=i+dx[k];
int yy=j+dy[k];
if(xx>=1&&yy<=m&&xx<=n&&yy>=1){
add_edge(id(i,j),id(xx,yy),inf);
}
}
}
}
}
printf("%lld",sum-dinic());
return 0;
}
10.P4012 深海机器人问题
题意:k个机器人从出发点到终点路径上的最大价值是多少,每个格子只能取一次
思路:直接按照题意建图即可
源点连一条容量为 K K K到出发点的边,表示有 K K K个机器人
拆点:
连一条边流量为
1
1
1,费用
w
w
w,代表只能取走一次价值
连一条边流量 i n f inf inf,费用 0 0 0,代表每个点可以走无限次
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=20005,maxm=20005;
struct edge{
int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
e[++cnt].v=v;
e[cnt].cost=cost;
e[cnt].flow=flow;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].cost=-cost;
e[cnt].flow=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
queue<int>q;
memset(dis,0x3f,sizeof dis);
memset(flow,0x3f,sizeof flow);
memset(inque,0,sizeof inque);
q.push(s);
inque[s]=1;
dis[s]=0;
pre[t]=-1;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!inque[v]){
q.push(v);
inque[v]=1;
}
}
}
}
if(pre[t]!=-1)return true;
return false;
}
inline pair<int,int> mcmf(int s,int t){
int maxflow=0;
int mincost=0;
while(spfa(s,t)){
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(u!=s){
e[last[u]].flow-=flow[t];
e[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
return {maxflow,mincost};
}
int p,q,a,b;
inline int id(int x,int y){
return x*(q+1)+y+1;
}
int main(){
cin>>a>>b>>p>>q;
int s=(p+1)*(q+1)+1;
int t=s+1;
for(int i=0;i<p+1;i++){
for(int j=0;j<q;j++){
int w;
cin>>w;
add_edge(id(i,j),id(i,j+1),-w,1);
add_edge(id(i,j),id(i,j+1),0,inf);
}
}
for(int j=0;j<q+1;j++){
for(int i=0;i<p;i++){
int w;
cin>>w;
add_edge(id(i,j),id(i+1,j),-w,1);
add_edge(id(i,j),id(i+1,j),0,inf);
}
}
for(int i=1;i<=a;i++){
int f,x,y;
cin>>f>>x>>y;
add_edge(s,id(x,y),0,f);
}
for(int i=1;i<=b;i++){
int f,x,y;
cin>>f>>x>>y;
add_edge(id(x,y),t,0,f);
}
pii yaoyao=mcmf(s,t);
cout<<-yaoyao.second;
}
11.P2762 太空飞行计划问题
题意:有一些实验可以获利,需要花钱买一些仪器才能完成,问净收益最大的试验计划
思路:
最大权闭合子图
建二分图,实验在左边,仪器在右边
割掉左边的边代表不选择这个实验,放弃它的价值,割右边的边代表选择付出这个代价,用价值总和-最小割即为答案
(对最大权闭合子图的理解已经在之前的博客写过,不再赘述)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
vector<int>yq,sy;
const int dx[]={1,-1,0,0};
const int dy[]={0,0,-1,1};
int cnt=1,n,m,s,t,qq;
inline int read(int &x){
char c;x=0;
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return c=='\r'||c=='\n'?0:1;
}
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(v>=m+1&&v<=m+n){
sy.push_back(u);
yq.push_back(v);
}
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
inline int dd(int x,int y){
return (x-1)*m+y;
}
int f[maxn],id[maxn],sum;
signed main(){
scanf("%lld%lld",&m,&n);
int u,v,w;
s=0,t=n+m+1;
for(int i=1;i<=m;i++){
qq=read(w);
add_edge(s,i,w);
sum+=w;
while(qq){
qq=read(w);
add_edge(i,m+w,inf);
}
}
for(int i=1;i<=n;i++){
scanf("%lld",&w);
add_edge(m+i,t,w);
}
int yaoyao=dinic();
for(int i=1;i<=m;i++){
if(dep[i])printf("%d ",i);
}
printf("\n");
for(int i=1;i<=n;i++){
if(dep[i+m])printf("%d ",i);
}
printf("\n%lld",sum-yaoyao);
return 0;
}
12.P2770 航空路线问题
题意:从起点到终点走一个来回,求最多能经过多少个城市(起点终点可经过两次)
思路:
因为求一来一回有点不方便,先将边转化为全部去终点,转化成求起点去终点的两条不相交路径
先跑一发费用流,求出最大经过城市数量
终点在于怎么输出路径,这里利用走过的边流量为 0 0 0的性质,跑两次 d f s dfs dfs即可
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{
int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
e[++cnt].v=v;
e[cnt].cost=cost;
e[cnt].flow=flow;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].cost=-cost;
e[cnt].flow=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
queue<int>q;
memset(dis,0x3f,sizeof dis);
memset(flow,0x3f,sizeof flow);
memset(inque,0,sizeof inque);
q.push(s);
inque[s]=1;
dis[s]=0;
pre[t]=-1;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!inque[v]){
q.push(v);
inque[v]=1;
}
}
}
}
if(pre[t]!=-1)return true;
return false;
}
string str[105];
unordered_map<string,int>mp;
inline pair<int,int> mcmf(int s,int t){
int maxflow=0;
int mincost=0;
int tim=0;
while(spfa(s,t)){
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
++tim;
while(u!=s){
e[last[u]].flow-=flow[t];
e[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
return {maxflow,mincost};
}
int vis[1005];
vector<string>jl1,jl2;
void dfs1(int u){
jl1.push_back(str[u]);
// cout<<str[u]<<endl;
vis[u]=1;
for(int i=head[u+n];i;i=e[i].nex){
int v=e[i].v;
if(e[i].flow!=0||vis[v]==1||v>n||v<1)continue;
else{
dfs1(v);
break;
}
}
}
void dfs2(int u){
// vis[u]=1;
jl2.push_back(str[u]);
for(int i=head[u+n];i;i=e[i].nex){
int v=e[i].v;
if(e[i].flow!=0||vis[v]==1||v>n||v<1)continue;
dfs2(v);
}
// cout<<str[u]<<endl;
}
int main(){
cin>>n>>m;
int num=0;
int s=1;
int t=n*2;
bool ok=false;
for(int i=1;i<=n;i++){
string a;
cin>>a;
mp[a]=i;
str[i]=a;
if(i==1||i==n){
add_edge(i,i+n,-1,2);
}
else{
add_edge(i,i+n,-1,1);
}
}
for(int i=1;i<=m;i++){
string a,b;
cin>>a>>b;
int x=mp[a];
int y=mp[b];
if(x>y)swap(x,y);
if(x==1&&y==n)ok=true;
add_edge(x+n,y,0,1);
}
pii yaoyao=mcmf(s,t);
if(yaoyao.first==2){
printf("%d\n",-yaoyao.second-2);
dfs1(s);
dfs2(s);
for(auto v:jl1){
cout<<v<<endl;
}
string zz[105];
int numzz=0;
for(auto v:jl2){
zz[++numzz]=v;
}
for(int i=numzz;i>=1;i--){
cout<<zz[i]<<endl;
}
}
else if(yaoyao.first==1&&ok){
cout<<2<<endl;
cout<<str[1]<<endl;
cout<<str[n]<<endl;
cout<<str[1]<<endl;
}
else{
printf("No Solution!");
}
return 0;
}
13.P2754 [CTSC1999]家园 / 星际转移问题
题意:有n个太空站,m艘飞船按照一定路线来回飞行,求地球到月球的最短时间
思路:
分层图网络流
每过一天建一套站点,借用一下luogu大佬题解的图片
进行解释一下,样例的走法是:
d
1
d1
d1:到
1
1
1号点
d
2
d2
d2:到
2
2
2号点
d
3
d3
d3:不走
d
4
d4
d4:不走
d
5
d5
d5:走到月球
因为飞船是不会停下来的,有的时候就必须在原地等飞船走到这个路线
每一次循环建立一套如图所示的站点
飞船的路线之间建立容量为 c a p cap cap的边,代表飞船一次能带这么多的人
前一天的这个点和后一天的这个点建立容量为 i n f inf inf的边(代表所有人都可以继续在这个站点等飞船过来)
然后不断跑最大流,直到 s u m > = k sum>=k sum>=k
设定一个较大的天数,如果超过了就break,代表不能将所有人都运输到月球
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],cap[maxn],cz[maxn],mp[205][205];
int cnt=1,n,m,s,t,k;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof head);
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
int u,v,w;
n+=2;
for(int i=1;i<=m;i++){
scanf("%d%d",&cap[i],&cz[i]);
for(int j=0;j<cz[i];j++){
scanf("%d",&mp[i][j]);
mp[i][j]+=2;
}
}
int day=0;
s=0,t=9999;
int sum=0;
while(day<500){
add_edge(s,day*n+2,inf);
add_edge(day*n+1,t,inf);
if(day){
for(int i=1;i<=n;i++){
add_edge((day-1)*n+i,day*n+i,inf);
}
for(int i=1;i<=m;i++){
int x=mp[i][(day-1)%cz[i]];
int y=mp[i][day%cz[i]];
add_edge((day-1)*n+x,day*n+y,cap[i]);
}
}
sum+=dinic();
if(sum>=k)break;
day++;
}
if(day==500)printf("0");
else printf("%d",day);
return 0;
}
14.P3254 圆桌问题
题意:有n个单位,m张餐桌,求每个单位的人不跟同单位的人在一个餐桌的方案
思路:
源点向每个单位建一条容量为单位人数的边
每个餐桌向汇点建一条容量为餐桌容纳的边
每个单位向每张餐桌连一条容量为1的边,代表各单位只能一个人选择这张餐桌
跑最大流
利用走过的路径反向边流量为1的性质,跑 d f s dfs dfs记录每个单位的人的选择即可
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t;
vector<int>jl[maxn];
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=1;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
void dfs2(int u){
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(v==t)return;
if(v>=1&&v<=n){
dfs2(v);
}
if(e[i^1].w==1&&v>=n+1&&v<=n+m){
jl[u].push_back(v-n);
}
}
return;
}
int main(){
int sum=0;
cin>>n>>m;
s=n+m+1;
t=n+m+2;
for(int i=1;i<=n;i++){
int x;
cin>>x;
sum+=x;
add_edge(s,i,x);
}
for(int i=1;i<=m;i++){
int x;
cin>>x;
add_edge(i+n,t,x);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
add_edge(i,j+n,1);
}
}
int yaoyao=dinic();
if(sum!=yaoyao)printf("0\n");
else{
dfs2(s);
printf("1\n");
for(int i=1;i<=n;i++){
for(auto v:jl[i]){
printf("%d ",v);
}
printf("\n");
}
}
return 0;
}
15.P1251 餐巾计划问题
16.P2763 试题库问题
题意:假设一个试题库中有 nn 道试题。每道试题都标明了所属类别。同一道题可能有多个类别属性。现要从题库中抽取 mm 道题组成试卷。并要求试卷包含指定类型的试题。
思路:
每个类型向汇点建一条容量为此类型需要的题目数量的边即可
增广路的过程中记录这个类型选择的题目
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t,k;
vector<int>jl[maxn];
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(v>=n+1&&v<=n+k){
jl[v-n].push_back(u);
}
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main(){
cin>>k>>n;
s=0,t=n+k+1;
int x;
for(int i=1;i<=k;i++){
scanf("%d",&x);
add_edge(n+i,t,x);
m+=x;
}
int p;
for(int i=1;i<=n;i++){
scanf("%d",&p);
add_edge(0,i,1);
for(int j=1;j<=p;j++){
scanf("%d",&x);
add_edge(i,n+x,1);
}
}
int ss=dinic();
if(ss<m){
printf("No Solution!");
}
else{
for(int i=1;i<=k;i++){
printf("%d:",i);
for(int j=0;j<jl[i].size();j++){
printf(" %d",jl[i][j]);
}
printf("\n");
}
}
return 0;
}
17.P2766 最长不下降子序列问题
题意:
t
a
s
k
1
task1
task1:求
l
i
s
lis
lis
t a s k 2 task2 task2:每个点取一次,求有多少个长度为 s s s的 l i s lis lis
t a s k 3 task3 task3:首尾可以取无限次,求有多少个长度为 s s s的 l i s lis lis
思路:
t
a
s
k
1
task1
task1:
d
p
dp
dp求
l
i
s
lis
lis,并记录每个点的值(到此为止的
l
i
s
lis
lis长度)
t a s k 2 task2 task2:每个点与 l i s lis lis为相邻长度的点连边,跑出最大流即为答案
t a s k 3 task3 task3:在前一问的条件下把首尾点容量改为无限即可
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t,ss,ll;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof head);
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int a[maxn],dp[maxn];
inline void init(){
add_edge(s,1,inf);
add_edge(1,1+n,inf);
if(dp[n]==ll){
add_edge(n+n,t,inf);
add_edge(n,n+n,inf);
}
}
int main(){
scanf("%d",&n);
int u,v,w;
s=0,t=n+n+1;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
dp[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<i;j++){
if(a[j]<=a[i]){
dp[i]=max(dp[i],dp[j]+1);
}
}
}
for(int i=1;i<=n;i++){
ll=max(ll,dp[i]);
// cout<<dp[i]<<" ";
}
cout<<ll<<endl;
for(int i=1;i<=n;i++){
add_edge(i,i+n,1);
if(dp[i]==1){
add_edge(s,i,1);
}
if(dp[i]==ll){
add_edge(i+n,t,1);
}
for(int j=1;j<i;j++){
if(a[j]<=a[i]&&dp[j]+1==dp[i]){
add_edge(j+n,i,1);
}
}
}
int ff=dinic();
printf("%d\n",ff);
init();
ff+=dinic();
printf("%d\n",ff<1e9?ff:1);
return 0;
}
18.P3355 骑士共存问题
题意:有一些障碍,计算棋盘上最多可以放置多少个骑士,使得它们彼此互不攻击
思路:
这题跟方格取数那一题的思想是一样的,每个点有与它限制的位置的点,但是有有所不同,这一题本质上是求最大独立集(因为互相不能攻击到),而那一题有点权,是求最小割
染色后将能互相攻击的位置连边,求出最大匹配
总点数-障碍数-最大匹配=最大独立集
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int inf =0x3f3f3f3f,maxn=2e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn];
const int dx[]={1,1,-1,-1,2,-2,2,-2};
const int dy[]={2,-2,2,-2,1,1,-1,-1};
int cnt=1,n,m,s,t;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=t+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
inline int id(int x,int y){
return (x-1)*n+y;
}
int mp[505][505];
signed main(){
scanf("%lld%lld",&n,&m);
int u,v,w;
s=0,t=n*n+1;
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
mp[x][y]=1;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(mp[i][j])continue;
if((i+j)%2==1){
add_edge(s,id(i,j),1);
}
else if((i+j)%2==0){
add_edge(id(i,j),t,1);
}
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if((i+j)%2==1&&!mp[i][j]){
for(int k=0;k<8;k++){
int xx=i+dx[k];
int yy=j+dy[k];
if(xx>=1&&yy<=n&&xx<=n&&yy>=1){
add_edge(id(i,j),id(xx,yy),inf);
}
}
}
}
}
printf("%lld",n*n-m-dinic());
return 0;
}
19.P3357 最长k可重线段集问题
20.P4013 数字梯形问题
t
a
s
k
1
task1
task1:不允许有路径相交:
很简单,一看就是点和边流量限制都为1,这样就不可能有右边的流跨到左边去了
t
a
s
k
2
task2
task2:允许选重复的点:
点的流量限制无限即可,主要点跟汇点的流量也要改掉
t
a
s
k
3
task3
task3:允许路径相交,允许重复点:
点边都无限即可
建图三次
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=40005,maxm=400005;
struct edge{
int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
e[++cnt].v=v;
e[cnt].cost=cost;
e[cnt].flow=flow;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].cost=-cost;
e[cnt].flow=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
queue<int>q;
memset(dis,0x3f,sizeof dis);
memset(flow,0x3f,sizeof flow);
memset(inque,0,sizeof inque);
q.push(s);
inque[s]=1;
dis[s]=0;
pre[t]=-1;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!inque[v]){
q.push(v);
inque[v]=1;
}
}
}
}
if(pre[t]!=-1)return true;
return false;
}
inline pair<int,int> mcmf(int s,int t){
int maxflow=0;
int mincost=0;
while(spfa(s,t)){
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(u!=s){
e[last[u]].flow-=flow[t];
e[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
return {maxflow,mincost};
}
inline int id(int x,int y){
return (x-1)*(m+n-1)+y;
}
int mp[1000][1000];
int main(){
cin>>m>>n;
int num=10000;
int k=m-1;
int s=20001;
int t=20002;
for(int i=1;i<=n;i++){
k++;
for(int j=1;j<=k;j++){
cin>>mp[i][j];
add_edge(id(i,j),id(i,j)+num,-mp[i][j],1);
if(i==1){
add_edge(s,id(i,j),0,1);
}
if(i==n){
add_edge(id(i,j)+num,t,0,1);
}
}
}
k=m-1;
for(int i=1;i<=n-1;i++){
k++;
for(int j=1;j<=k;j++){
add_edge(id(i,j)+num,id(i+1,j),0,1);
add_edge(id(i,j)+num,id(i+1,j+1),0,1);
}
}
pii yaoyao1=mcmf(s,t);
cout<<-yaoyao1.second<<endl;
memset(head,0,sizeof head);
cnt=1;
for(int i=1;i<=n;i++){
k++;
for(int j=1;j<=k;j++){
// cin>>mp[i][j];
add_edge(id(i,j),id(i,j)+num,-mp[i][j],inf);
if(i==1){
add_edge(s,id(i,j),0,1);
}
if(i==n){
add_edge(id(i,j)+num,t,0,inf);
}
}
}
k=m-1;
for(int i=1;i<=n-1;i++){
k++;
for(int j=1;j<=k;j++){
add_edge(id(i,j)+num,id(i+1,j),0,1);
add_edge(id(i,j)+num,id(i+1,j+1),0,1);
}
}
pii yaoyao2=mcmf(s,t);
cout<<-yaoyao2.second<<endl;
memset(head,0,sizeof head);
cnt=1;
for(int i=1;i<=n;i++){
k++;
for(int j=1;j<=k;j++){
// cin>>mp[i][j];
add_edge(id(i,j),id(i,j)+num,-mp[i][j],inf);
if(i==1){
add_edge(s,id(i,j),0,1);
}
if(i==n){
add_edge(id(i,j)+num,t,0,inf);
}
}
}
k=m-1;
for(int i=1;i<=n-1;i++){
k++;
for(int j=1;j<=k;j++){
add_edge(id(i,j)+num,id(i+1,j),0,inf);
add_edge(id(i,j)+num,id(i+1,j+1),0,inf);
}
}
pii yaoyao3=mcmf(s,t);
cout<<-yaoyao3.second<<endl;
return 0;
}
21.P3356 火星探险问题
题意:有一些障碍,有些点上面有矿石,有n辆车,只能向左或向下走,求采到最多矿石的路径
思路:建图跟深海那题一样,利用反向边流量的性质跑 d f s dfs dfs记录路径即可
#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int inf=0x3f3f3f3f,maxn=20005,maxm=400005;
struct edge{
int nex,v,cost,flow;
}e[maxm];
int head[maxn],cnt=1;
int n,m,k,T;
inline void add_edge(int u,int v,int cost,int flow){
e[++cnt].v=v;
e[cnt].cost=cost;
e[cnt].flow=flow;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].cost=-cost;
e[cnt].flow=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
int inque[maxn],dis[maxn],flow[maxn],pre[maxn],last[maxn];
bool spfa(int s,int t){
queue<int>q;
memset(dis,0x3f,sizeof dis);
memset(flow,0x3f,sizeof flow);
memset(inque,0,sizeof inque);
q.push(s);
inque[s]=1;
dis[s]=0;
pre[t]=-1;
while(!q.empty()){
int u=q.front();
q.pop();
inque[u]=0;
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].cost&&e[i].flow){
dis[v]=dis[u]+e[i].cost;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].flow);
if(!inque[v]){
q.push(v);
inque[v]=1;
}
}
}
}
if(pre[t]!=-1)return true;
return false;
}
inline pair<int,int> mcmf(int s,int t){
int maxflow=0;
int mincost=0;
while(spfa(s,t)){
int u=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(u!=s){
e[last[u]].flow-=flow[t];
e[last[u]^1].flow+=flow[t];
u=pre[u];
}
}
return {maxflow,mincost};
}
inline int id(int x,int y){
return (x-1)*m+y;
}
inline int id2(int x,int y){
return (x-1)*m+y+n*m;
}
int vis[maxn];
void dfs(int u,int num){
for(int i=head[u+n*m];i;i=e[i].nex){
int v=e[i].v;
if(v==u+1&&vis[i]<e[i^1].flow){
printf("%d 1\n",num);
vis[i]++;
dfs(v,num);
break;
}
if(v==u+m&&vis[i]<e[i^1].flow){
printf("%d 0\n",num);
vis[i]++;
dfs(v,num);
break;
}
}
}
int mp[105][105];
int main(){
cin>>k>>m>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>mp[i][j];
if(mp[i][j]==1)continue;
add_edge(id(i,j),id2(i,j),mp[i][j]==2?-1:0,1);
add_edge(id(i,j),id2(i,j),0,inf);
}
}
int s=n*m*2+1;
int t=s+1;
if(mp[1][1]!=1)add_edge(s,id(1,1),0,k);
if(mp[n][m]!=1)add_edge(id2(n,m),t,0,k);
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(mp[i][j]==1)continue;
if(mp[i][j+1]!=1&&j+1<=m){
add_edge(id2(i,j),id(i,j+1),0,inf);
}
if(mp[i+1][j]!=1&&i+1<=n){
add_edge(id2(i,j),id(i+1,j),0,inf);
}
}
}
pii yaoyao=mcmf(s,t);
for(int i=1;i<=k;i++){
dfs(1,i);
}
return 0;
}
22.P2765 魔术球问题
题意:
假设有
n
n
n 根柱子,现要按下述规则在这
n
n
n 根柱子中依次放入编号为
1
1
1,
2
2
2,
3
3
3,…的球“
-
每次只能在某根柱子的最上面放球。
-
同一根柱子中,任何 2 2 2 个相邻球的编号之和为完全平方数。
试设计一个算法,计算出在 n n n 根柱子上最多能放多少个球。例如,在 4 4 4 根柱子上最多可放 11 11 11 个球。
对于给定的 n n n,计算在 n n n 根柱子上最多能放多少个球。
思路:
枚举n,拆点,求出小于n能和n组成平方数的数,连边,在残量网络上跑网络流,如果能找到新的增广路代表现有的柱子上还可以放,否则增加柱子,在增广路的过程中记录每个柱子上的数,跟最小路径覆盖那题异曲同工
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn],vis[maxn];
int cnt=1,n,m,s=100001,t=100002,now,p;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
memcpy(cur,head,sizeof head);
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
if(u!=t)to[u>>1]=v>>1;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int zz[maxn];
int main(){
scanf("%d",&n);
int p=0;
while(p<=n) {
now++;
add_edge(s,now*2,1);
add_edge(now*2+1,t,1);
// printf("s %d\n",now);
// printf("%d+%d t\n",now,now);
for(int i=sqrt(now)+1;i*i<now*2;i++) {
add_edge((i*i-now)*2,now*2+1,1);
// printf("%d %d+%d\n",(i*i-now),now,now);
}
int flow = dinic();
if(!flow){
zz[++p] = now;
}
}
cout<<now-1<<endl;
for(int i=1;i<=n;i++){
int u=zz[i];
printf("%d",u);
u=to[u];
while(u&&u!=t>>1){
printf(" %d",u);
u=to[u];
}
cout<<endl;
}
return 0;
}
23.P2764 最小路径覆盖问题
给定有向图 G = ( V , E ) G=(V,E) G=(V,E) 。设 P P P 是 G G G 的一个简单路(顶点不相交)的集合。如果 V V V 中每个定点恰好在 P P P的一条路上,则称 P P P 是 G G G 的一个路径覆盖。 P P P中路径可以从 V V V 的任何一个定点开始,长度也是任意的,特别地,可以为 0 0 0 。 G G G 的最小路径覆盖是 G G G 所含路径条数最少的路径覆盖。设计一个有效算法求一个 GAP (有向无环图) G G G 的最小路径覆盖。
提示:设 V = { 1 , 2 , . . . , n } V=\{1,2,...,n\} V={1,2,...,n} ,构造网络 G 1 = { V 1 , E 1 } G_1=\{V_1,E_1\} G1={V1,E1} 如下:
V 1 = { x 0 , x 1 , . . . , x n } ∪ { y 0 , y 1 , . . . , y n } V_1=\{x_0,x_1,...,x_n\}\cup\{y_0,y_1,...,y_n\} V1={x0,x1,...,xn}∪{y0,y1,...,yn}
E 1 = { ( x 0 , x i ) : i ∈ V } ∪ { ( y i , y 0 ) : i ∈ V } ∪ { ( x i , y j ) : ( i , j ) ∈ E } E_1=\{(x_0,x_i):i\in V\}\cup\{(y_i,y_0):i\in V\}\cup\{(x_i,y_j):(i,j)\in E\} E1={(x0,xi):i∈V}∪{(yi,y0):i∈V}∪{(xi,yj):(i,j)∈E}
每条边的容量均为 1 1 1 ,求网络 G 1 G_1 G1 的 ( x 0 , y 0 ) (x_0,y_0) (x0,y0) 最大流。
题目直接给出了提示,可是我太蒻了没有看懂…
原图是这样子的:
要我们求最小覆盖路径,我们可以先把图看成没有边,此时覆盖路径数为11,通过手玩可以发现,每当我们连上一条边之后,这个路径数就会减少1,而每一条路径上,一个点只能被另外一个点连上,这就符合匹配的原理,我们由此构建出二分图:
可以看出最小路径覆盖数=总点数-最大流,因为最大流即为匹配数,被匹配上的点就不需要做出发点
题目要求我们输出路径,在dfs找增广路的时候记录下每一个点 u − > v u->v u−>v的 v v v就好
#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t;
struct edge{
int v,w,nex;
}e[maxm*2];
inline void add_edge(int u,int v,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].nex=head[u];
head[u]=cnt;
e[++cnt].v=u;
e[cnt].w=0;
e[cnt].nex=head[v];
head[v]=cnt;
}
bool bfs(){
memset(dep,0,sizeof(dep));
for(int i=0;i<=2*n+5;i++){//如果要拆点或者其他的一定记得把范围开大点!!!
cur[i]=head[i];
}
dep[s]=1;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nex){
int v=e[i].v;
if(!dep[v]&&e[i].w){
dep[v]=dep[u]+1;
q.push(v);
if(v==t)return true;
}
}
}
return false;
}
int dfs(int u,int now){
if(u==t||now==0){
return now;
}
int flow=0,rlow=0;
for(int i=cur[u];i;i=e[i].nex){
int v=e[i].v;
if(e[i].w&&dep[v]==dep[u]+1){
if(rlow=dfs(v,min(now,e[i].w))){
flow+=rlow;
now-=rlow;
e[i].w-=rlow;
e[i^1].w+=rlow;
to[u]=v;
if(u!=s)flag[v-n]=1;
if(now==0)return flow;
}
}
}
if(!flow)dep[u]=-1;
return flow;
}
int dinic(){
int maxflow=0;
while(bfs()){
maxflow+=dfs(s,inf);
}
return maxflow;
}
int main(){
scanf("%d%d",&n,&m);
int u,v,w;
s=0,t=2*n+1;
for(int i=1;i<=n;i++){
add_edge(s,s+i,1);
add_edge(s+n+i,t,1);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&u,&v);
add_edge(s+u,s+n+v,1);
}
int ans=dinic();
for(int i=1;i<=n;i++){
if(!flag[i]){
int now=i;
printf("%d ",now);
while(to[now]&&to[now]!=t){
printf("%d ",to[now]-n);
now=to[now]-n;
}
cout<<endl;
}
}
printf("%d",n-ans);
return 0;
}
24.P2775 机器人路径规划问题
著名假题,可以用ida*过,咕 咕 咕~