网络流专题



很明显的一道最大流匹配题目
唯一要注意的点是题目要求是一头牛只能搭配一个饮料和事物
所以要拆点

点击查看代码
#include
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define INF 0x7fffffff
const int maxn=505;
const int maxm=200000;
int N,F,D,cnt,S,T;
int head[maxm],num[maxn],maxflow;
struct node{
	int to,next,w;
}edg[maxm];
void add(int u,int v,int w){
	++cnt;
	edg[cnt].to=v;
	edg[cnt].w=w;
	edg[cnt].next=head[u];
	head[u]=cnt;
}
queueQ;
bool bfs(){
	while(!Q.empty()){
	   Q.pop();
	}
	for(int i=S;i<=T+N;i++)num[i]=-1;
	num[S]=1;
	Q.push(S);
	while(!Q.empty()){
		int now=Q.front();Q.pop();
		for(int i=head[now];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&num[to]==-1){
			num[to]=num[now]+1;
			Q.push(to);
		}
		} 
	} 
	return (num[T]!=-1);
}
int dfs(int u,int f){
	if(u==T){
		return f;
	}
	int flow;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&num[to]==num[u]+1&&(flow=dfs(to,min(f,w)))){
			edg[i].w-=flow;
			edg[i^1].w+=flow;
			return flow;
		}
	}
	return 0;
}
void dinic(){
	int minn;
	while(bfs()){
		while(minn=dfs(S,INF)){
			maxflow+=minn;
		}
	}
}
int main(){
	scanf("%d%d%d",&N,&F,&D);
	cnt++;
	S=1,T=1+N+F+D+1;
	for(int i=1;i<=F;i++)add(S,i+1,1),add(i+1,S,0);
	for(int i=1;i<=D;i++)add(i+1+F+N,T,1),add(T,i+1+F+N,0);
	for(int i=1;i<=N;i++)add(1+F+i,1+F+N+D+1+i,1),add(1+F+N+D+i+1,1+F+i,0);
	for(int i=1;i<=N;i++){
		int ff,dd;
		scanf("%d%d",&ff,&dd);
		for(int t,j=1;j<=ff;j++){
			scanf("%d",&t);
			add(t+1,1+F+i,1);
			add(1+F+i,t+1,0);
		}
		for(int t,j=1;j<=dd;j++){
			scanf("%d",&t);
			add(1+F+N+D+1+i,1+F+N+t,1);
			add(1+F+N+t,1+F+N+D+1+i,0);
		}
	}
	dinic();
	printf("%d\n",maxflow);
     return 0;
}

其实网络流的题目还是很明显的
你读完题目就知道这个是网络流的题目
建边
超源点S连接每个试题,因为每个试题只能选一次,所以边权为1
每个试题连接多个不同的类型,因为每个试题只能代表一种类型,所以边权为1
每个类型连接超汇点T,因为每个类型有不同的要求数量,所以边权具体题目给出
输出
这个题关键就是输出
记得dinic算法是建立了反向边的,所以只要该类型通向试题的反向边权大于0,就可以输出

点击查看代码
#include
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
#define INF 0x7fffffff
const int maxn=2e3+5;
const int maxm=1e6+5;
int K,N,M,S,T,ans,cnt;
int head[maxm],num[maxn],vis[maxn];
queueQ;
struct node{
	int to,w,next;
}edg[maxm];
void add(int u,int v,int w){
	++cnt;
	edg[cnt].to=v;
	edg[cnt].w=w;
	edg[cnt].next=head[u];
	head[u]=cnt;
}
bool bfs(){
	while(!Q.empty()){
		Q.pop();
	}
	for(int i=S;i<=T;i++)vis[i]=-1;
	vis[S]=1;
	Q.push(S);
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,w=edg[i].w;
			if(w&&vis[to]==-1){
				vis[to]=vis[u]+1;
				Q.push(to);
			}
		}
	}
	return (vis[T]!=-1);
}
int dfs(int u,int f){
	if(u==T)return f;
	int flow=0;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&vis[to]==vis[u]+1&&(flow=dfs(to,min(w,f)))){
			edg[i].w-=flow;
			edg[i^1].w+=flow;
			return flow;
		}
	}
	return 0;
}
void dinic(){
	while(bfs()){
		int ff;
		while(ff=dfs(S,INF)){
			ans+=ff;
		}
	}
}
void print(int u){
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w>0&&to<=S+N)
		printf(" %d",to-S);
	}
}
int main(){
	scanf("%d%d",&K,&N);
	++cnt;S=1;T=1+N+K+1;
	for(int i=1;i<=K;i++){
		scanf("%d",&num[i]),M+=num[i];
		add(S+N+i,T,num[i]);
		add(T,S+N+i,0);
	}
	for(int i=1;i<=N;i++)add(S,S+i,1),add(S+i,S,0);
	for(int i=1;i<=N;i++){
		int p;scanf("%d",&p);
		while(p--){
			int t;scanf("%d",&t);
			add(S+i,S+N+t,1);
			add(S+N+t,S+i,0);
		}
	}
	dinic();
	if(ans


分析
很好能get到这个题是最小割的题目
关键的点就是在于朋友之间建边要双向建边,一开始我一直没想明白,做题的时候也很难想到
那就说明对最小割的理解还不够深刻
一般我们建立S->T的单向边是因为只有S流向T,而这个题不一样
尽管A和B两人的意见不一样,那可以B妥协A,A可以妥协B,就是说
S可以流向T,T同样可以流向S

点击查看代码
  #include
    #define il inline
    using namespace std;
    const int N=100005,inf=23333333;
    int n,m,s,t=520,h[N],cnt=1,dis[N],ans;
    struct edge{
    int to,net,v;
    }e[N*4];
    il void add(int u,int v,int w)
    {
        e[++cnt].to=v,e[cnt].net=h[u],e[cnt].v=w,h[u]=cnt;
        e[++cnt].to=u,e[cnt].net=h[v],e[cnt].v=0,h[v]=cnt;
    }
    queueq;
    il bool bfs()
    {
        memset(dis,-1,sizeof(dis));
        q.push(s),dis[s]=0;
        while(!q.empty())
        {
            int u=q.front();q.pop();
            for(int i=h[u];i;i=e[i].net)
            if(dis[e[i].to]==-1&&e[i].v>0)dis[e[i].to]=dis[u]+1,q.push(e[i].to);
        }
        return dis[t]!=-1;
    }
    il int dfs(int u,int op)
    {
        if(u==t)return op;
        int used=0;
        for(int i=h[u];i;i=e[i].net)
        {
            int v=e[i].to;
            if(dis[v]==dis[u]+1&&e[i].v>0)
            {
                used=dfs(v,min(op,e[i].v));
                if(!used)continue;
                e[i].v-=used,e[i^1].v+=used;
               return used;
            }
        }
        return 0;
    }
    int main()
    {
        scanf("%d%d",&n,&m);
        int x,y;
        for(int i=1;i<=n;i++){
            scanf("%d",&x);
            if(x==1)add(s,i,1);
            else add(i,t,1);
        }
        for(int i=1;i<=m;i++){
            scanf("%d%d",&x,&y);
            add(x,y,1),add(y,x,1);
        }
        while(bfs())ans+=dfs(s,inf);
        cout<


这是最小割的经典题目类型
最大权闭合图
建图:
S与每个实验相连,边权为资金费
T与每个仪器相连,边权为消耗费用(相当于已经取绝对值了)
每个实验与相应的仪器相连,边权设为无穷大(因为割边一定不能割实验与仪器的边)
最后就是跑一遍dinic最大流
最大流=最小割
最大权闭合子图的权值和 = max{被选择的点权和} = 正点权和?min{没被选择的正权点之和 + 被选择的负权点绝对值和} = 正点权和?最小割
最后一个要解决的问题就是要输出这个最大权闭合图
考虑最后一次bfs,能够到达的点就是在该图内

点击查看代码
#include
using namespace std;
#define lowbit(x) x&(-x)
#define inf 0x7fffffff
#define ll long long
const int maxn=155;
const int maxm=1e6;
int S,T,M,N,cnt,tot,ans;
int head[maxn],dp[maxn];
struct node{
	int to,w,next;
}edg[maxm];
void add(int u,int v,int w){
	edg[++cnt].next=head[u];edg[cnt].to=v;edg[cnt].w=w;head[u]=cnt;
	edg[++cnt].next=head[v];edg[cnt].to=u;edg[cnt].w=0;head[v]=cnt;
}
queueQ;
bool bfs(){
	while(!Q.empty()){
		Q.pop();
	}
	for(int i=S;i<=T;i++)dp[i]=-1;
	dp[S]=1;
	Q.push(S);
	while(!Q.empty()){
		int u=Q.front();
		Q.pop();
		for(int i=head[u];i;i=edg[i].next){
			int to=edg[i].to,w=edg[i].w;
			if(w&&dp[to]==-1){
				dp[to]=dp[u]+1;
				Q.push(to);
			}
		}
	}
	return dp[T]!=-1;
}
int dfs(int u,int f){
	if(u==T)return f;
	int flow=0;
	for(int i=head[u];i;i=edg[i].next){
		int to=edg[i].to,w=edg[i].w;
		if(w&&dp[to]==dp[u]+1&&(flow=dfs(to,min(f,w)))){
			edg[i].w-=flow;
			edg[i^1].w+=flow;
			return flow;
		}
	}
	return 0;
}
void dinic(){
	while(bfs()){
		int minn;
		while(minn=dfs(S,inf)){
			ans+=minn;
		}
	}
	
}
int main(){
	++cnt;
	S=0;T=150;
	scanf("%d%d",&M,&N);
    for (int i = 1,c; i <= M; i++) {
		scanf("%d", &c), tot += c;
		add(S, i, c);
		while (getchar() == ' ') {
			scanf("%d", &c);
			add(i, c + M, inf);
		}
	}
	for(int i=1,c;i<=N;i++){
		scanf("%d",&c);
		add(i+M,T,c);
	}
	dinic();
	for (int i = 1; i <= M; i++) if (dp[i]!=-1) cout << i << ' '; puts("");
	for (int i = 1; i <= N; i++) if (dp[i + M]!=-1) cout << i << ' '; puts("");
	printf("%d\n",tot-ans);
     return 0;
}


又是一道最大流模板
因为每个人只能选一个房间,所以对每个人进行拆点,最后套个dinic模板即可

点击查看代码
#include
using namespace std;
#define open(s) freopen( s".in", "r", stdin ), freopen( s".out", "w", stdout )
#define MAXN 405
#define MAXM 40005

int n, p, q;
int hd[MAXN], nxt[MAXM << 1], to[MAXM << 1], val[MAXM << 1], tot(1);
int ans, dis[MAXN];
queue Q;

int x, y;
int S, T;

void Add( int x, int y, int z ){ nxt[++tot] = hd[x]; hd[x] = tot; to[tot] = y; val[tot] = z; }

bool BFS(){
	while( !Q.empty() ) Q.pop();
	memset( dis, 0, sizeof dis );
	Q.push(S); dis[S] = 1;
	while( !Q.empty() ){
		x = Q.front(); Q.pop();
		for ( int i = hd[x]; i; i = nxt[i] )
			if ( val[i] && !dis[to[i]] ){
				dis[to[i]] = dis[x] + 1;
				Q.push( to[i] );
				if ( to[i] == T ) return 1;
			}
	}
	return 0;
}

int DFS( int x, int fl ){
	if ( x == T ) return fl;
	int  k;
	for ( int i = hd[x]; i ; i = nxt[i] ){
		if ( val[i] && dis[to[i]] == dis[x] + 1 ){
			k = DFS( to[i], min( fl, val[i] ) );
			if(!k)continue;
			val[i] -= k; val[i^1] += k; 
			return k;
		}
	}
	return 0;
}

int main(){
	scanf( "%d%d%d", &n, &p, &q );
	S = 0; T = 1 + n + n + p + q;
	for ( int i = 1; i <= n; ++i ) Add( i, i + n, 1 ), Add( i + n, i, 0 );
	for ( int i = 1; i <= p; ++i ) Add( S, i + n + n, 1 ), Add( i + n + n, S, 0 );
	for ( int i = 1; i <= q; ++i ) Add( i + n + n + p, T, 1 ), Add( T, i + n + n + p, 0 );
	
	for ( int i = 1; i <= n; ++i )
		for ( int j = 1; j <= p; ++j ){
			int t; scanf( "%d", &t );
			if ( t ) Add( j + n + n, i, 1 ), Add( i, j + n + n, 0 );
		}
	for ( int i = 1; i <= n; ++i )
		for ( int j = 1; j <= q; ++j ){
			int t; scanf( "%d", &t );
			if ( t ) Add( i + n, j + n + n + p, 1 ), Add( j + n + n + p, i + n, 0 );
		}
	int t;
	while( BFS() )
		while( ( t = DFS( S, 0x7f7f7f7f ) ) > 0 ) ans += t;
	printf( "%d\n", ans );
	return 0;
}


这个题目特别点在于要求删点
那么就拆点:
x拆为(x,x')容量为1
原图边x->y 为无向边,所以变为x'->y和y'->x容量均为无穷大
割点x相当于割掉x->x'这条边
因为要经过x连通的边没有了x->x'这条边都联通不了
因为至少满足两个点不连通整个图就不联通了
最后枚举源点和汇点即可

点击查看代码
const int N = 100, M = 5e4+7, INF = 0x3f3f3f3f;
int s1,t1,n,m;
int head[N<<1],ver[M],nex[M],edge[M],tot;
int a[N * N],b[N * N],deep[N<<1];

inline void add(int x,int y,int z){//正边反边
    ver[++tot] = y;edge[tot] = z;
    nex[tot] = head[x];head[x] = tot;
    ver[++tot] = x;edge[tot] = 0;
    nex[tot] = head[y];head[y] = tot;
}

inline bool bfs(){
    memset(deep,0,sizeof deep);
    queueq;
    q.push(s1);
    deep[s1] = 1;//分层
    while(q.size()){
        int x = q.front();
        q.pop();
        for(int i = head[x];i;i = nex[i]){
            int y = ver[i],z = edge[i];//剩余容量>0才属于残量网络
            if(z > 0 && !deep[y]){//不只是更新deep数组,是在残量网络上更新deep数组
                q.push(y);
                deep[y] = deep[x] + 1;
                if(y == t1)return true;
            }
        }
    }
    return false;
}

inline int dinic(int x,int flow){
    if(x == t1)return flow;
    int res = flow;
    for(int i = head[x];i && res;i = nex[i]){
        int y = ver[i],z = edge[i];
        if(z > 0 && (deep[y] == deep[x] + 1)){
            int k = dinic(y,min(res,z));
            if(!k)deep[y] = 0;
            edge[i] -= k;
            edge[i ^ 1] += k;
            res -= k;
        }
    }
    return flow - res;
}

int main(){
    while(cin>>n>>m){
        for(int i = 0;i < m;++i){
            char str[20];
            scanf("%s",str);
            a[i] = b[i] = 0;
            int j;
            for(j = 1;str[j] != ',';j++)
                a[i] = a[i] * 10 + (str[j] - '0');
            for(j++;str[j] != ')';j++)
                b[i] = b[i] * 10 + (str[j] - '0');
        }
        int ans = INF;
        for (s1 = 0; s1 < n; s1++)
		for (t1 = 0; t1 < n; t1++)
        if(s1 != t1){
            memset(head,0,sizeof head);
            tot = 1;
            int maxflow = 0;
            for(int i = 0;i < n;++i){
                if(i == s1 || i == t1)//i是入点,i+n是出点
                     add(i,i + n,INF);//防止被割断
                else add(i,i + n,1);
            }
            for(int i = 0;i < m;++i){
                add(a[i] + n,b[i],INF);//不能割
                add(b[i] + n,a[i],INF);
            }
            while(bfs()){
                int num;
                while((num = dinic(s1,INF)))
                    maxflow += num;
            }
            ans = min(ans,maxflow);
        }
        if(n <= 1 || ans == INF)ans = n;
        cout<