网络最大流学习笔记
学姐说什么DINIC自己学她就不讲了。。。然而这是啥东西我都还不知道。。百度了一下发现是网络流的一种实现方式。。蒟蒻就被逼无奈的啃这个不好吃的网络流了。
首先说一下什么是网络流吧:算了不想说了,蒟蒻是从某大佬的博客中学会的,挂上大佬的博客链接。。实际上百度第三个就是。。
这里
然后蒟蒻就开始做题了,首先当然是那个板子题:
1 网络最大流模板
没什么好说的,直接套上板子就可以,真的裸裸的板子,别的啥都没有。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7fffffff
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[1000001];
int head[1000001],w[100001],dep[1000001],n,m,s,t,u,num=-1,x,y,l;
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
w[num]=dis;
head[from]=num;
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(w[i]>0&&dep[u]==0)
{
dep[u]=dep[now]+1;
q.push(u);
}
}
}
if(dep[t]==0)
return 0;
return 1;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
for(re int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&w[i]!=0)
{
int check=dfs(v,min(w[i],dis));
if(check>0)
{
w[i]-=check;
w[i^1]+=check;
return check;
}
}
}
return 0;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
while(int d=dfs(s,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
n=read(),m=read();s=read();t=read();
for(re int i=1;i<=m;i++)
{
cin>>x>>y>>l;
add_edge(x,y,l);
add_edge(y,x,0);
}
cout<<dinic();
}
事先声明,这个是绝对裸体的板子,虽然蒟蒻笔者的常数优化还挺不错的,但是依然挽救不了很多题直接这么写会T的事实。然后旁边祝神看不下去,随手给我敲了一个优化,直接优化了100+ms,额。。。
优化如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7fffffff
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[1000001];
int head[1000001],w[1000001],dep[1000001],n,m,s,t,u,num=-1,x,y,l;
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
w[num]=dis;
head[from]=num;
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(w[i]>0&&dep[u]==0)
{
dep[u]=dep[now]+1;
q.push(u);
}
}
}
if(dep[t]==0)
return 0;
return 1;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int i=head[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&w[i]!=0)
{
int check=dfs(v,min(w[i],dis));
if(check>0)
{
diss+=check;
dis-=check;
w[i]-=check;
w[i^1]+=check;
if(!dis) break;
}
}
}
return diss;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
while(int d=dfs(s,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
n=read(),m=read();s=read();t=read();
for(re int i=1;i<=m;i++)
{
cin>>x>>y>>l;
add_edge(x,y,l);
add_edge(y,x,0);
}
cout<<dinic();
}
然后祝神简单易懂的解释了这个东西的来源,就是博客那个弧优化。然后,莫名懵逼,博客里那个每次用完之后还要O(n)的时间来初始化,这个压根就没有复杂度。那还要博客里那个干啥。。。。这个等会再实践,来看第一道题。
2.【P1231】教辅的组成
通过瞟了一眼题解立刻明白怎么建图,然后直接裸的板子扔上去。。然后,样例过不了爆0了好长时间。。。经过仔细的寻找,终于发现了一个微小的失误,原因就是check搜索完之后并没有return。。。。这个居然找了将近半个小时,恶心。然后交上之后,T7个点。。。。。。心中一群草泥马飞奔过去。然后开始看优化,就填上了那个弧优化,其实也不是很麻烦,只是一步而已。然后就直接快了近10倍A掉了。。。先放这份代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7fffffff
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[1000001];
int head[1000001],cur[100001],dep[60001],n,m,s,t,u,num=-1,x,y,l,n1,n2,n3;
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[1]=1;
q.push(1);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(dep[u]==0&&edge[i].dis>0)
{
dep[u]=dep[now]+1;
q.push(u);
}
}
}
if(dep[t]==0)
return 0;
return 1;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&edge[i].dis!=0)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
edge[i].dis-=check;
edge[i^1].dis+=check;
return check;
}
}
}
return 0;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
for(re int i=1;i<=t;i++)
{
cur[i]=head[i];
}
while(int d=dfs(1,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
n1=read(),n2=read(),n3=read();
m=read();
for(re int i=1;i<=m;i++)
{
x=read();y=read();
add_edge(y+1,x+1+n2,1);
add_edge(x+n2+1,y+1,0);
}
for(re int i=1;i<=n1;i++)
{
add_edge(i+n2+1,i+n2+n1+1,1);
add_edge(i+n2+n1+1,i+n2+1,0);
}
m=read();
for(re int i=1;i<=m;i++)
{
x=read();y=read();
add_edge(x+n2+n1+1,y+n2+2*n1+1,1);
add_edge(y+n2+2*n1+1,x+n2+n1+1,0);
}
for(re int i=1;i<=n2;i++)
{
add_edge(1,i+1,1);
add_edge(i+1,1,0);
}
for(re int i=1;i<=n3;i++)
{
add_edge(i+1+n2+n1+n1,2+n2+n1+n1+n3,1);
add_edge(2+n2+n1+n1+n3,i+1+n2+n1+n1,0);
}
t=2+n1+n1+n2+n3;
cout<<dinic();
}
觉得不说这个题的做法有点不太好,说实话可以看出每一本书对于每一个教辅和答案的匹配是唯一且绝对的,所以可以跑最大流,每条边的容量就是1,然后因为如果书只算一本的话容量很不好处理,所以把一本书拆成两本。这个题可以有很多个源点和汇点,所以添加一个超级源和一个超级汇,跑一遍DINIC即可。
然后事实证明祝神说的方法和现在用的还是有不同的。。。如果这么干了,这个题会T掉10个点。。
提供一些更快的优化,首先手打队列自不必说,但是牵扯到memeset容易得不偿失,如果可以O2的话还是直接STL。
快读也自不必说,这些题的读入量都绝对不小。
然后bfs的时候一旦搜到了汇点直接退出去,不要等到队列为空。
然后放上应该是不能更快的的:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7fffffff
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[1000001];
int head[1000001],cur[100001],dep[60001],n,m,s,t,u,num=-1,x,y,l,n1,n2,n3;
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[1]=1;
q.push(1);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(dep[u]==0&&edge[i].dis>0)
{
dep[u]=dep[now]+1;
if(u==t)
return 1;
q.push(u);
}
}
}
if(dep[t]==0)
return 0;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&edge[i].dis!=0)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
diss+=check;
dis-=check;
edge[i].dis-=check;
edge[i^1].dis+=check;
if(dis==0) break;
}
}
}
return diss;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
for(re int i=1;i<=t;i++)
{
cur[i]=head[i];
}
while(int d=dfs(1,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
n1=read(),n2=read(),n3=read();
m=read();
for(re int i=1;i<=m;i++)
{
x=read();y=read();
add_edge(y+1,x+1+n2,1);
add_edge(x+n2+1,y+1,0);
}
for(re int i=1;i<=n1;i++)
{
add_edge(i+n2+1,i+n2+n1+1,1);
add_edge(i+n2+n1+1,i+n2+1,0);
}
m=read();
for(re int i=1;i<=m;i++)
{
x=read();y=read();
add_edge(x+n2+n1+1,y+n2+2*n1+1,1);
add_edge(y+n2+2*n1+1,x+n2+n1+1,0);
}
for(re int i=1;i<=n2;i++)
{
add_edge(1,i+1,1);
add_edge(i+1,1,0);
}
for(re int i=1;i<=n3;i++)
{
add_edge(i+1+n2+n1+n1,2+n2+n1+n1+n3,1);
add_edge(2+n2+n1+n1+n3,i+1+n2+n1+n1,0);
}
t=2+n1+n1+n2+n3;
cout<<dinic();
}
其实因为题目数据量的原因,一些数组可以卡着来减少memset的时间复杂度取得更快的速度。
3【P2936】全流
这个题没啥写做法的意义了,直接板子就能0ms过掉。。。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7fffffff
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[1000001];
int head[1000001],cur[100001],dep[60001],n,m,s,t,u,num=-1,x,y,l,n1,n2,n3;
char cx,cy;
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline int change(char ch)
{
if(ch>='A'&&ch<='Z')
return ch-'A'+1;
return ch-'a'+27;
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[1]=1;
q.push(1);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(edge[i].dis>0&&dep[u]==0)
{
dep[u]=dep[now]+1;
if(u==t)
return 1;
q.push(u);
}
}
}
return 0;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&edge[i].dis!=0)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
diss+=check;
dis-=check;
edge[i].dis-=check;
edge[i^1].dis+=check;
if(dis==0) break;
}
}
}
return diss;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
for(re int i=1;i<=52;i++)
cur[i]=head[i];
while(int d=dfs(1,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
cin>>n;
for(re int i=1;i<=n;i++)
{
cin>>cx>>cy>>l;
x=change(cx);
y=change(cy);
add_edge(x,y,l);
add_edge(y,x,0);
}
s=1;t=26;
cout<<dinic();
}
4【P2756】飞行员配对方案问题
这个题跟刚才第一个题类似,因为是配对问题,又是一个多源的图,所以构建超级源点和汇点,s到每个外籍飞行员连容量为1的边,每个英国飞行员到t连容量为1的边。
至于输出方案,因为这个题的特殊性,从我们设置的源点到汇点的路径最多只经过三条边,所以排除掉与源点和汇点相连的两边之后,剩下的一定就是两个飞行员之间的连边。这时我们在判断一下这条边是否在增广的时候被使用过了,就可以知道点了。要注意的是,这个题要先输出外籍飞行员,所以你要先输出反边到达的点。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define inf 50000000
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[100001];
int head[100001],cur[100001],dep[60001],n,m,s,t,u,num=-1,x,y,l,tot,sum;
inline int read()
{
int xx=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')xx=xx*10+ch-'0',ch=getchar();
return xx*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline bool bfs()
{
queue<int> q;
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis>0&&dep[v]==0)
{
dep[v]=dep[now]+1;
if(v==t)
return 1;
q.push(v);
}
}
}
return 0;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis!=0&&dep[v]==dep[u]+1)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
diss+=check;
dis-=check;
edge[i].dis-=check;
edge[i^1].dis+=check;
if(dis==0) break;
}
}
}
return diss;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
for(re int i=0;i<=n+1;i++)
cur[i]=head[i];
while(int d=dfs(0,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
m=read();n=read();
x=read();y=read();
while(x!=-1&&y!=-1)
{
add_edge(x,y,inf);
add_edge(y,x,0);
x=read();y=read();
}
s=0,t=n+1;
for(re int i=1;i<=m;i++)
{
add_edge(s,i,1);
add_edge(i,s,0);
}
for(re int i=m+1;i<=n;i++)
{
add_edge(i,t,1);
add_edge(t,i,0);
}
int answer=dinic();
if(answer==0)
{
cout<<"No Solution!";
return 0;
}
cout<<answer<<endl;
for(re int i=0;i<=num;i+=2)
if(edge[i].to!=s&&edge[i].to!=t&&edge[i^1].to!=s&&edge[i^1].to!=t)
if(edge[i].dis!=inf)
cout<<edge[i^1].to<<" "<<edge[i].to<<endl;
return 0;
}
5.【P3410】拍照
这个题看上去很懵,输入给的不伦不类,但是这个毕竟是网络流这个标签下的题。。。所以开始考虑怎么建边。考虑到添加超级源和超级汇,考虑到如何把方案和人连起来,然后,就不会了。从题解处得知,这东西叫做最大权闭合子图然后跑最大流,总流量就是最优方案的总花费;
然后,总收益=总价值-总花费。
额。。。。
那就来吧。
结果从上午一直wa到下午,祝神看了两眼,你这没建反边啊。。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define inf 50000000
#define re register
using namespace std;
struct po
{
ll from,to,dis,nxt;
}edge[100001];
ll head[100001],cur[100001],dep[6001],n,m,s,t,u,num=-1,x,y,l,tot,sum;
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline void add(int from,int to,int dis)
{
add_edge(from,to,dis);
add_edge(to,from,0);
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[0]=1;
q.push(0);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re ll i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(edge[i].dis>0&&dep[u]==0)
{
dep[u]=dep[now]+1;
if(u==t)
return 1;
q.push(u);
}
}
}
return 0;
}
inline ll dfs(ll u,ll dis)
{
if(u==t)
return dis;
int diss=0;
for(re ll& i=cur[u];i!=-1;i=edge[i].nxt)
{
ll v=edge[i].to;
if(dep[v]==dep[u]+1&&edge[i].dis!=0)
{
ll check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
edge[i].dis-=check;
edge[i^1].dis+=check;
return check;
}
}
}
return 0;
}
inline ll dinic()
{
ll ans=0;
while(bfs())
{
for(re int i=0;i<=n+m+1;i++)
cur[i]=head[i];
while(int d=dfs(0,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
n=read();m=read();
s=0,t=n+m+1;
for(re int i=1;i<=n;i++)
{
l=read();
tot+=l;
add(s,i,l);
while(cin>>y&&y!=0)
add(i,y+n,inf);
}
for(re int i=1;i<=m;i++)
{
l=read();
add(i+n,t,l);
}
sum=tot-dinic();
if(sum>0)
cout<<sum;
else
cout<<0;
}
6.【P2055】假期的宿舍
这个题真是让人。。。实际上看上去应该有的正解应该是二分图。然而网络流看上去也可以做。跟以前一样DINIC就是个板子,关键又在于建图。然后蒟蒻首先考虑的是把所有在校的学生住宿的与s连边,所有外校的与t连边,然而这样啥作用都没有。事实上我们可以看出人数是一定的,提供的床位一定要达到要住校的人数才可以。然后比较显而易见的是把所有要住宿的与s连边,所有能提供床的与t连边,然后跑一遍最大流,判断一下是否比要住的学生人数多就可以了。
要注意的是,这个题有
多组数据
所以每次重新算的时候要把
所有数据
初始化,没初始化tot一直全wa。。。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define inf 50000000
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[100001];
int head[100001],cur[100001],dep[60001],n,m,s,t,u,num=-1,x,y,l,tot,sum,T,a[100],b[101];
inline int read()
{
int xx=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')xx=xx*10+ch-'0',ch=getchar();
return xx*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline bool bfs()
{
queue<int> q;
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis>0&&dep[v]==0)
{
dep[v]=dep[now]+1;
if(v==t)
return 1;
q.push(v);
}
}
}
return 0;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis!=0&&dep[v]==dep[u]+1)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
diss+=check;
dis-=check;
edge[i].dis-=check;
edge[i^1].dis+=check;
if(dis==0) break;
}
}
}
return diss;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
for(re int i=0;i<=2*n+1;i++)
cur[i]=head[i];
while(int d=dfs(0,inf))
ans+=d;
}
return ans;
}
inline void prepare()
{
memset(head,-1,sizeof(head));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
num=-1;tot=0;
}
int main()
{
T=read();
while(T--)
{
prepare();
cin>>n;
s=0;t=2*n+1;
for(re int i=1;i<=n;i++)
cin>>a[i];
for(re int i=1;i<=n;i++)
cin>>b[i];
for(re int i=1;i<=n;i++)
{
if(a[i]==1)
{
add_edge(i+n,t,1);
add_edge(t,n+i,0);
}
if(a[i]==1&&b[i]==0||a[i]==0)
{
tot++;
add_edge(s,i,1);
add_edge(i,s,0);
}
}
for(re int i=1;i<=n;i++)
{
if(a[i]==1&&b[i]==0)
{
add_edge(i,i+n,1);
add_edge(n+i,i,0);
}
for(re int j=1;j<=n;j++)
{
cin>>x;
if(x==1)
{
add_edge(i,n+j,1);
add_edge(n+j,i,0);
}
}
}
if(dinic()>=tot)
cout<<"^_^"<<endl;
else
cout<<"T_T"<<endl;
}
}
7.【P1345】奶牛的电信
这个题刚开始看的时候,觉得是最小割。。然后一想,最小割=最大流,裸题啊!然后自信满满的复制过来板子,建了个很SB的图,交上去,诶,怎么就70 ,还T了一个点???然后开始各种找错误。。。发现绝对没有任何问题。点开题解之后,发现并不是裸题。。人家最小割是割边,这题是割点。。。说一下建图的方法:
首先把每个点都拆成两个点,然后把自己和自己连边,流量为1。对于1n的点,让他们只连到自己身上,对于n+12n的点,按照给的关系连到1n上,流量为inf。这样就可以保证,如果割掉了ii+n的那条边,我们就让i这个点不存在了。
然后几个容易错的地方:
(1)如果用了弧优化的话,cur那个地方的初始化循环的上界要是2 * n。
(2)一开始的源点是s+n而不是s,否则再大的点割掉源点和汇点之后它也可以不通信,只会输出1|2.。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define inf 50000000
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[100001];
int head[100001],cur[100001],dep[60001],n,m,s,t,u,num=-1,x,y,l,tot,sum,T,a[100],b[101];
inline int read()
{
int xx=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')xx=xx*10+ch-'0',ch=getchar();
return xx*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline bool bfs()
{
queue<int> q;
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis>0&&dep[v]==0)
{
dep[v]=dep[now]+1;
if(v==t)
return 1;
q.push(v);
}
}
}
return 0;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis!=0&&dep[v]==dep[u]+1)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
diss+=check;
dis-=check;
edge[i].dis-=check;
edge[i^1].dis+=check;
if(dis==0) break;
}
}
}
return diss;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
for(re int i=1;i<=201;i++)
cur[i]=head[i];
while(int d=dfs(s,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
n=read();m=read();s=read();t=read();
s+=n;
for(re int i=1;i<=n;i++)
{
add_edge(i,i+n,1);
add_edge(n+i,i,0);
}
for(re int i=1;i<=m;i++)
{
x=read();y=read();
add_edge(x+n,y,inf);
add_edge(y,x+n,0);
add_edge(y+n,x,inf);
add_edge(x,y+n,0);
}
cout<<dinic();
}
8.【P2711】小行星
这个题刚开始看的时候,mmp呦,啥东西,根本看不出来怎么建图啊。然而经过长时间的对数据的观察之后,发现了一个清奇的建图方法,就是把每个面当做一个点,然后建立超级源和超级汇,把x面与s连,z面与y连,剩下的按照给的关系连边,求这张图的最小割。之后打出程序,改了一些小瑕疵了之后,居然A了。想了一下,觉得正确性显然,就看了一眼题解,然后发现题解里的程序和我的十分相似,但是他多干了一件事,把y拆成两个点然后自连,把前面的y和x连,后面的y和z连。思考了好长时间之后,发现确实是对的。割边的时候,x与源点相当于消除x面,y之间的相当于消除y面,z和汇点之间的相当于消除z面,这样最小割就是我们要求的答案。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define inf 50000000
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[1000001];
int head[1000001],cur[1000001],dep[100001],n,m,s,t,z,u,num=-1,x,y,l,tot,sum,T,a[100],b[101];
inline int read()
{
int xx=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')xx=xx*10+ch-'0',ch=getchar();
return xx*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline void add(int from,int to,int dis)
{
add_edge(from,to,dis);
add_edge(to,from,0);
}
inline bool bfs()
{
queue<int> q;
memset(dep,0,sizeof(dep));
while(!q.empty())
q.pop();
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis>0&&dep[v]==0)
{
dep[v]=dep[now]+1;
if(v==t)
return 1;
q.push(v);
}
}
}
return 0;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(edge[i].dis!=0&&dep[v]==dep[u]+1)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
diss+=check;
dis-=check;
edge[i].dis-=check;
edge[i^1].dis+=check;
if(dis==0) break;
}
}
}
return diss;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
for(re int i=0;i<=2001;i++)cur[i]=head[i];
while(int d=dfs(s,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
n=read();
s=0;t=2001;
for(re int i=1;i<=n;i++)
{
x=read();y=read();z=read();
add(x,y+500,1);
add(y+500,y+1000,1);
add(y+1000,z+3*500,1);
}
for(re int i=1;i<=500;i++)
{
add(s,i,1);
add(i+1500,t,1);
}
cout<<dinic();
}
9.【P1402】酒店之王
这个题。。。愧对紫题,难度虚高,还没有那个蓝色的教辅的组成来的难。。。然而做法是一样的,换个读入就可以了。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define inf 0x7fffffff
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[1000001];
int head[1000001],cur[100001],dep[60001],n,m,s,t,u,num=-1,x,y,l,p,q,flag;
inline int read()
{
int x=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
return x*c;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline void add(int from,int to,int dis)
{
add_edge(from,to,dis);
add_edge(to,from,0);
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(dep[u]==0&&edge[i].dis>0)
{
dep[u]=dep[now]+1;
if(u==t)
return 1;
q.push(u);
}
}
}
if(dep[t]==0)
return 0;
}
inline int dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&edge[i].dis!=0)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
diss+=check;
dis-=check;
edge[i].dis-=check;
edge[i^1].dis+=check;
if(dis==0) break;
}
}
}
return diss;
}
inline int dinic()
{
int ans=0;
while(bfs())
{
for(re int i=0;i<=t;i++)
{
cur[i]=head[i];
}
while(int d=dfs(s,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
n=read();p=read();q=read();
s=0;t=n+n+p+q+1;
for(re int i=1;i<=n;i++)
{
for(re int j=1;j<=p;j++)
{
flag=read();
if(flag==1)
add(j,i+p,1);
}
}
for(re int i=1;i<=n;i++)
for(re int j=1;j<=q;j++)
{
flag=read();
if(flag==1)
add(i+n+p,j+n+n+p,1);
}
for(re int i=1;i<=n;i++)
add(i+p,i+n+p,1);
for(re int i=1;i<=p;i++)
add(s,i,1);
for(re int i=1;i<=q;i++)
add(i+n+n+p,t,1);
cout<<dinic();
}
10.【P2762】太空飞行计划问题
这道题看完题面的一瞬间,这题做过,就是那个调了老半天的拍照,仔细一看,如果不算输出方案的话,就是拍照的样例。。。然后直接搬运,增加了输出方案,就是直接输出最后一次分层图深度不是0的点。为什么呢?因为我们最后一次跑分层图的时候,肯定是fasle出去的,然而此时还能有深度的点一定就是用过的了。然后最坑的地方,狗屎读入!!!!东找西找怎么都看不出错来但就是不过样例。。。然后讨论里一群对读入怨声载道的,我就改了自己的读入,然后特地看了讨论里要把\n改成\r,结果,啥都读不出来啊!!!然后仔细想了好长时间,样例应该不是windos造的吧,又改回去,然而还是不过。。。最后仔细又看了好长时间,while循环这么跑会吃掉最后一个数。。。一切都改完之后,终于过样例了。。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define inf 50000000
#define re register
using namespace std;
struct po
{
int from,to,dis,nxt;
}edge[100001];
int head[100001],cur[100001],dep[6001],n,m,s,t,u,num=-1,x,y,l,tot,sum;
inline bool read()
{
x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='\r') return false;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
if(ch=='\r')
return false;
return true;
}
inline void add_edge(int from,int to,int dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline void add(int from,int to,int dis)
{
add_edge(from,to,dis);
add_edge(to,from,0);
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re ll i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(edge[i].dis>0&&dep[u]==0)
{
dep[u]=dep[now]+1;
if(u==t)
return 1;
q.push(u);
}
}
}
return 0;
}
inline ll dfs(int u,int dis)
{
if(u==t)
return dis;
int diss=0;
for(re int& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&edge[i].dis!=0)
{
int check=dfs(v,min(dis,edge[i].dis));
if(check>0)
{
edge[i].dis-=check;
edge[i^1].dis+=check;
return check;
}
}
}
return 0;
}
inline ll dinic()
{
ll ans=0;
while(bfs())
{
for(re int i=0;i<=n+m+1;i++)
cur[i]=head[i];
while(int d=dfs(s,inf))
ans+=d;
}
return ans;
}
int main()
{
memset(head,-1,sizeof(head));
cin>>n>>m;
s=0,t=n+m+1;
for(re int i=1;i<=n;i++)
{
cin>>l;
tot+=l;
add(s,i,l);
while(1)
{
if(!read())
{
y=x;
add(i,y+n,inf);
break;
}
y=x;
add(i,y+n,inf);
}
}
for(re int i=1;i<=m;i++)
{
cin>>l;
add(i+n,t,l);
}
sum=tot-dinic();
for(re int i=1;i<=n;i++)
if(dep[i]!=0)
cout<<i<<" ";
cout<<endl;
for(re int i=n+1;i<=n+m;i++)
if(dep[i]!=0)
cout<<i-n<<" ";
cout<<endl;
cout<<sum;
}
11.【P3324】星际战争
这个题比之前两个都难好不好,然而比那个读入坑爹的要容易做不少。
首先我们看出这个题可以最大流出来应该花费的时间,然后我们自然会看到那个“最少需要的时间”,自然想到二分。建图的时候,从S往激光连一条边权为inf的边,激光往机器人连边权为inf的边,机器人往T连边权为bi的边。——转自学长题解。这样跑了第一遍最大流之后我们得到一个值,然后依靠这个值,我们二分最少需要的时间,并且把一开始的激光打机器人从inf变成a[i]*你check的那个值,然后重新建图,再跑最大流,看一下这个值和刚才一不一样。根据学长所说,这个题对于精度限制不是很大,所以可以利用先乘再除避免使用double,但是记住,这么做要开long long。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define ll long long
#define re register
using namespace std;
struct po
{
ll from,to,dis,nxt;
}edge[200001];
ll head[2001],cur[100001],dep[1001],n,m,s,t,num=-1,x,y,l,tot,sum,map[201][201];
ll a[1001],b[1001],inf=50000000;
inline int read()
{
int xx=0,c=1;
char ch=' ';
while((ch>'9'||ch<'0')&&ch!='-')ch=getchar();
while(ch=='-')c*=-1,ch=getchar();
while(ch<='9'&&ch>='0')xx=xx*10+ch-'0',ch=getchar();
return xx*c;
}
inline void add_edge(int from,int to,ll dis)
{
edge[++num].nxt=head[from];
edge[num].to=to;
edge[num].dis=dis;
head[from]=num;
}
inline void add(int from,int to,ll dis)
{
add_edge(from,to,dis);
add_edge(to,from,0);
}
inline bool bfs()
{
queue<int> q;
while(!q.empty())
q.pop();
memset(dep,0,sizeof(dep));
dep[s]=1;
q.push(s);
while(!q.empty())
{
int now=q.front();
q.pop();
for(re int i=head[now];i!=-1;i=edge[i].nxt)
{
int u=edge[i].to;
if(dep[u]==0&&edge[i].dis>0)
{
dep[u]=dep[now]+1;
if(u==t)
return 1;
q.push(u);
}
}
}
return 0;
}
inline ll dfs(int u,ll dis)
{
if(u==t)
return dis;
ll diss=0;
for(re ll& i=cur[u];i!=-1;i=edge[i].nxt)
{
int v=edge[i].to;
if(dep[v]==dep[u]+1&&edge[i].dis!=0)
{
ll checkr=dfs(v,min(dis,edge[i].dis));
if(checkr>0)
{
diss+=checkr;
dis-=checkr;
edge[i].dis-=checkr;
edge[i^1].dis+=checkr;
if(dis==0) break;
}
}
}
return diss;
}
inline ll dinic()
{
ll ans=0;
while(bfs())
{
for(re int i=0;i<=t;i++)
{
cur[i]=head[i];
}
while(ll d=dfs(s,inf))
ans+=d;
}
return ans;
}
inline bool check(ll v)
{
memset(head,-1,sizeof(head));
memset(edge,0,sizeof(edge));
num=-1;
for(re int i=1;i<=n;i++)
add(i+m,t,a[i]*10000);
for(re int i=1;i<=m;i++)
{
add(s,i,b[i]*v);
for(re int j=1;j<=n;j++)
if(map[i][j])
add(i,j+m,inf);
}
ll cnt=dinic();
return cnt==sum;
}
int main()
{
inf*=10000;
memset(head,-1,sizeof(head));
n=read();m=read();
s=0;t=n+m+1;
for(re int i=1;i<=n;i++)
{a[i]=read();add(i+m,t,a[i]*10000);}
for(re int i=1;i<=m;i++)
{b[i]=read();add(s,i,inf);}
for(re int i=1;i<=m;i++)
for(re int j=1;j<=n;j++)
{
map[i][j]=read();
if(map[i][j])
add(i,j+m,inf);
}
sum=dinic();
ll l=1,r=100000000;
while(l<=r)
{
ll mid=l+r>>1;
if(check(mid))
r=mid-1;
else
l=mid+1;
}
printf("%.4lf",(r+1)*1.0/10000);
}