本节内容——
- 网络流问题及常见模型
例题27 我是SAM
说是SAM但是并没有什么关系
给出一个R*C大小的网络,网格上面放了一些目标。可以在网格外发射子弹,子弹会沿着垂直或者水平方向飞行,并且打掉飞行路径上的所有目标。你的任务是计算出最少需要多少子弹,各从哪些位置发射,才能把所有目标全部打掉。(还要输出任意一种合法方案)
就是一个二分图最小覆盖问题,把X坐标(一行)和Y坐标(一列)划分成二分图,如果有一个目标位置为(x,y),那么连行X和列Y。
然后因为二分图最小点覆盖即最小割,我们直接上dinic即可。
麻烦的是如何输出任意一组最小割方案——
(如果只输出左右端点,不考虑中间连边——即INF时)跑一遍dinic,然后在残量网络上记录done数组,最后一遍bfs左边没有访问到的点和右边访问到的点就是最小割。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<queue>
#include<vector>
#define S 0
#define T n+m+1
#define MAXN 100010
#define INF 0x3f3f3f3f
using namespace std;
int n,m,t,k,top,tot,cnt;
int head[MAXN],cur[MAXN],dis[MAXN],done[MAXN];
int dfn[MAXN],low[MAXN],c[MAXN],st[MAXN],in[MAXN];
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
struct Node{char op;int pos;};
vector<Node>vec;
inline void add(int from,int to,int dis)
{
edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
edge[++t].nxt=head[to],edge[t].to=from,edge[t].dis=0,head[to]=t;
}
inline bool bfs()
{
queue<int>q;
memset(dis,0x3f,sizeof(dis));
memcpy(cur,head,sizeof(head));
memset(done,0,sizeof(done));
q.push(S);dis[S]=0;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis&&dis[v]==0x3f3f3f3f)
dis[v]=dis[u]+1,q.push(v),done[v]=1;
}
}
if(dis[T]==0x3f3f3f3f) return false;
return true;
}
inline int dfs(int x,int f)
{
if(!f||x==T) return f;
int used=0,w;
for(int i=cur[x];i;i=edge[i].nxt)
{
cur[x]=i;
int v=edge[i].to;
if(dis[v]==dis[x]+1&&(w=dfs(v,min(f,edge[i].dis))))
{
edge[i].dis-=w,edge[i^1].dis+=w;
f-=w,used+=w;
if(!f) break;
}
}
return used;
}
inline int dinic()
{
int cur_ans=0;
while(bfs()) cur_ans+=dfs(S,(int)1e9);
return cur_ans;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
freopen("ce.out","w",stdout);
#endif
while(scanf("%d%d%d",&n,&m,&k)==3)
{
if(n==0&&m==0&&k==0) break;
vec.clear();
memset(head,0,sizeof(head));
t=1,top=tot=cnt=0;
for(int i=1;i<=k;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add(x,y+n,INF);//printf("[%d %d] %d\n",x,y+n,INF);
}
for(int i=1;i<=n;i++) add(S,i,1);//printf("[%d %d] %d\n",S,i,1);
for(int i=1;i<=m;i++) add(i+n,T,1);//printf("[%d %d] %d\n",i+n,T,1);
int ans=dinic();
printf("%d ",ans);
for(int i=1;i<=n;i++)
if(!done[i])
{
Node cur;
cur.op='r',cur.pos=i;
vec.push_back(cur);
}
// printf("r%d ",i);
for(int i=1;i<=m;i++)
if(done[i+n])
{
Node cur;
cur.op='c',cur.pos=i;
vec.push_back(cur);
}
// printf("c%d ",i);
for(int i=0;i<vec.size()-1;i++) printf("%c%d ",vec[i].op,vec[i].pos);
printf("%c%d\n",vec[vec.size()-1].op,vec[vec.size()-1].pos);
}
return 0;
}
例题28 保守的老师
翻译自己看去.......
就是4个条件只要满足任意一个就要选取,也就是——4个条件全部不满足只能任选其一。那么我们考虑只要4个条件都不满足,就连一条边,然后跑二分图最大独立集就行了。(可以根据是男生还是女生分成二分图)
二分图最大独立集等于n-最大流。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define S 0
#define T n+1
#define MAXN 100010
#define INF 0x3f3f3f3f
using namespace std;
int tt,n,m,t,tot;
int head[MAXN],dis[MAXN],cur[MAXN];
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
struct Node{int a,id;char b;string c,d;}node[MAXN];
inline void add(int from,int to,int dis)
{
// if(from!=S&&to!=T) printf("[%d %d]\n",from,to);
edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
edge[++t].nxt=head[to],edge[t].to=from,edge[t].dis=0,head[to]=t;
}
inline bool bfs()
{
memset(dis,0x3f,sizeof(dis));
memcpy(cur,head,sizeof(head));
queue<int>q;
q.push(S);
dis[S]=0;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(dis[v]==0x3f3f3f3f&&edge[i].dis)
{
dis[v]=dis[u]+1;
q.push(v);
}
}
}
if(dis[T]==0x3f3f3f3f) return false;
return true;
}
inline int dfs(int x,int f)
{
if(x==T||!f) return f;
int used=0,w;
for(int i=cur[x];i;i=edge[i].nxt)
{
cur[x]=i;
if(dis[edge[i].to]==dis[x]+1&&(w=dfs(edge[i].to,min(f,edge[i].dis))))
{
used+=w,f-=w;
edge[i].dis-=w,edge[i^1].dis+=w;
if(!f) break;
}
}
return used;
}
inline int dinic()
{
int cur_ans=0;
while(bfs()) cur_ans+=dfs(S,INF);
return cur_ans;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
freopen("ce.out","w",stdout);
#endif
scanf("%d",&tt);
while(tt--)
{
memset(head,0,sizeof(head));
tot=0;
t=1;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
cin>>node[i].a>>node[i].b>>node[i].c>>node[i].d;
if(node[i].b=='M') tot++;
}
int num1=1,num2=1;
for(int i=1;i<=n;i++)
{
if(node[i].b=='M')
node[i].id=num1,num1++,add(S,node[i].id,1);
else
node[i].id=num2+tot,num2++,add(node[i].id,T,1);
}
// for(int i=1;i<=n;i++)
// printf("id=%d\n",node[i].id);
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(i==j) continue;
int flag=0;
if(abs(node[i].a-node[j].a)<=40) flag++;//cout<<1<<endl;
if(node[i].b!=node[j].b) flag++;//cout<<2<<endl;
if(node[i].c==node[j].c) flag++;//cout<<3<<endl;
if(node[i].d!=node[j].d) flag++;//cout<<4<<endl;
if(flag!=4) continue;
if(node[i].b=='M') add(node[i].id,node[j].id,1);
else add(node[j].id,node[i].id,1);
}
}
printf("%d\n",n-dinic());
}
return 0;
}
例题29 出租车
有m个人从城市的不同位置出发,到达它们各自的目的地。已知每个人的出发时间,出发地点和目的地。你的任务是用尽量少的出租车送他们,使得每次出租车接待客人时,至少能提前一分钟到达他所在的位置。注意,为了满足这一条件,要么这个人是这辆出租车接送的第一个人,要么在接送完上一个人后,有足够的时间从上一个目的地开到这里。