10.网络流

网络流

开题顺序: \(BEFKQCRDGPAHT\)

\(A\) luogu P3511 [POI 2010] MOS-Bridges

  • 考虑二分答案,现在需要解决的问题是将某些双向边进行定向。

  • 入度和出度的和是固定的,考虑每条边对端点入度的改变量跑最大流判是否能满流即可。

  • 最后根据满流的边跑一遍求解欧拉回路。

    点击查看代码
    const int inf=0x3f3f3f3f;
    int u[2010],v[2010],w[2][2010],du[2010],cur[2010],vis[2010];
    vector<pair<int,int> >e[2010];
    stack<int>s;
    struct MaxFlow
    {
    	struct node
    	{
    		int nxt,to,cap,flow;
    	}e[40010];
    	int head[4010],dis[4010],vis[4010],cur[4010],cnt=1;
    	void clear()
    	{
    		memset(e,0,sizeof(e));
    		memset(head,0,sizeof(head));
    		cnt=1;
    	}
    	void add(int u,int v,int w)
    	{
    		cnt++;  e[cnt]=(node){head[u],v,w,0};  head[u]=cnt;
    		cnt++;  e[cnt]=(node){head[v],u,0,0};  head[v]=cnt;
    	}
    	bool bfs(int s,int t)
    	{
    		memset(vis,0,sizeof(vis));
    		queue<int>q;
    		dis[s]=1;  cur[s]=head[s];
    		q.push(s);  vis[s]=1;
    		while(q.empty()==0)
    		{
    			int x=q.front();  q.pop();
    			for(int i=head[x];i!=0;i=e[i].nxt)
    			{
    				if(vis[e[i].to]==0&&e[i].cap>e[i].flow)
    				{
    					dis[e[i].to]=dis[x]+1;  cur[e[i].to]=head[e[i].to];
    					q.push(e[i].to);  vis[e[i].to]=1;
    					if(e[i].to==t)  return true;
    				}
    			}
    		}
    		return false;
    	}
    	int dfs(int x,int t,int flow)
    	{
    		if(x==t)  return flow;
    		int sum=0,tmp;
    		for(int i=cur[x];i!=0&&sum<flow;i=e[i].nxt)
    		{
    			cur[x]=i;
    			if(dis[e[i].to]==dis[x]+1&&e[i].cap>e[i].flow)
    			{
    				tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum));
    				if(tmp==0)  dis[e[i].to]=0;
    				sum+=tmp;
    				e[i].flow+=tmp;  e[i^1].flow-=tmp;
    			}
    		}
    		return sum;
    	}
    	int Dinic(int s,int t)
    	{
    		int flow=0;
    		while(bfs(s,t)==true)  flow+=dfs(s,t,inf);
    		return flow;
    	}
    }F;
    bool check(int n,int m,int mid,int sum)
    {
    	F.clear();
    	int s=n+m+1,t=n+m+2;
    	for(int i=1;i<=m;i++)
    	{
    		F.add(s,n+i,1);
    		if(w[0][i]<=mid)  F.add(n+i,v[i],1);
    		if(w[1][i]<=mid)  F.add(n+i,u[i],1);
    	}
    	for(int i=1;i<=n;i++)  F.add(i,t,du[i]/2);
    	return F.Dinic(s,t)>=sum;
    }
    void dfs(int x,int fa)
    {
    	for(int i=cur[x];i<e[x].size();i=max(i+1,cur[x]))
    	{
    		if(vis[e[x][i].second]==0)
    		{
    			vis[e[x][i].second]=1;
    			cur[x]=i+1;
    			dfs(e[x][i].first,e[x][i].second);
    		}
    	}
    	if(fa!=0)  s.push(fa);
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,m,l=0,r=1000,mid,ans=0,flag=1,sum=0,i,j;
    	cin>>n>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>u[i]>>v[i]>>w[0][i]>>w[1][i];
    		du[u[i]]++;  du[v[i]]++;
    		l=max(l,min(w[0][i],w[1][i]));
    	}
    	for(i=1;i<=n;i++)  
    	{
    		flag&=(du[i]%2==0);  sum+=du[i]/2;
    	}
    	if(flag==0)  cout<<"NIE"<<endl;
    	else
    	{
    		while(l<=r)
    		{
    			mid=(l+r)/2;
    			if(check(n,m,mid,sum)==true)
    			{
    				ans=mid;
    				r=mid-1;
    			}
    			else
    			{
    				l=mid+1;
    			}
    		}
    		cout<<ans<<endl;
    		check(n,m,ans,sum);
    		for(i=1;i<=m;i++)
    		{
    			for(j=F.head[n+i];j!=0;j=F.e[j].nxt)
    			{
    				if(F.e[j].cap==F.e[j].flow)
    				{
    					if(F.e[j].to==v[i])  e[u[i]].push_back(make_pair(v[i],i));
    					else  e[v[i]].push_back(make_pair(u[i],i));
    				}
    			}
    		}
    		dfs(1,0);
    		while(s.empty()==0)  
    		{
    			cout<<s.top()<<" ";
    			s.pop();
    		}
    	}
    	return 0;
    }
    

\(B\) luogu P2517 [HAOI2010] 订货

\(C\) luogu P3358 最长k可重区间集问题

\(D\) luogu P4001 [ICPC-Beijing 2006] 狼抓兔子

\(E\) luogu P4897 【模板】最小割树(Gomory-Hu Tree)

\(F\) luogu P4043 [AHOI2014/JSOI2014] 支线剧情

\(G\) UVA1306 The K-League

  • 考虑每两场队伍之间的比赛向两个队伍之间连边来分配胜负,通过判断最大流是否等于总比赛数得到是否合法。

  • 特判无论如何都不能获胜的情况。

    点击查看代码
    const int inf=0x3f3f3f3f;
    int w[50],a[50][50],sum[50];
    vector<int>ans;
    struct MaxFlow
    {
    	struct node
    	{
    		int nxt,to,cap,flow;
    	}e[12010];
    	int head[1510],dis[1510],vis[1510],cur[1510],cnt=1;
    	void clear()
    	{
    		memset(e,0,sizeof(e));
    		memset(head,0,sizeof(head));
    		cnt=1;
    	}
    	void add(int u,int v,int w)
    	{
    		cnt++;  e[cnt]=(node){head[u],v,w,0};  head[u]=cnt;
    		cnt++;  e[cnt]=(node){head[v],u,0,0};  head[v]=cnt;
    	}
    	bool bfs(int s,int t)
    	{
    		memset(vis,0,sizeof(vis));
    		queue<int>q;
    		dis[s]=1;  cur[s]=head[s];
    		q.push(s);  vis[s]=1;
    		while(q.empty()==0)
    		{
    			int x=q.front();  q.pop();
    			for(int i=head[x];i!=0;i=e[i].nxt)
    			{
    				if(vis[e[i].to]==0&&e[i].cap>e[i].flow)
    				{
    					dis[e[i].to]=dis[x]+1;  cur[e[i].to]=head[e[i].to];
    					q.push(e[i].to);  vis[e[i].to]=1;
    					if(e[i].to==t)  return true;
    				}
    			}
    		}
    		return false;
    	}
    	int dfs(int x,int t,int flow)
    	{
    		if(x==t)  return flow;
    		int sum=0,tmp;
    		for(int i=cur[x];i!=0&&sum<flow;i=e[i].nxt)
    		{
    			cur[x]=i;
    			if(dis[e[i].to]==dis[x]+1&&e[i].cap>e[i].flow)
    			{
    				tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum));
    				if(tmp==0)  dis[e[i].to]=0;
    				sum+=tmp;
    				e[i].flow+=tmp;  e[i^1].flow-=tmp;
    			}
    		}
    		return sum;
    	}
    	int Dinic(int s,int t)
    	{
    		int flow=0;
    		while(bfs(s,t)==true)  flow+=dfs(s,t,inf);
    		return flow;
    	}
    }F;
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int tesecase,n,s,t,x,num,flag,i,j,k;
    	cin>>tesecase;
    	for(;tesecase>=1;tesecase--)
    	{
    		ans.clear();
    		cin>>n;  num=0;  s=n*n+n+1;  t=n*n+n+2;
    		for(i=1;i<=n;i++)  cin>>w[i]>>x;
    		for(i=1;i<=n;i++)
    		{
    			sum[i]=w[i];
    			for(j=1;j<=n;j++)  
    			{
    				cin>>a[i][j];  
    				num+=a[i][j];  sum[i]+=a[i][j];
    			}
    		}
    		num/=2;
    		for(k=1;k<=n;k++)
    		{
    			F.clear();  flag=1;
    			for(i=1;i<=n;i++)
    			{
    				for(j=i+1;j<=n;j++)
    				{
    					F.add(s,(i-1)*n+j,a[i][j]);
    					F.add((i-1)*n+j,n*n+i,a[i][j]);
    					F.add((i-1)*n+j,n*n+j,a[i][j]);
    				}
    				F.add(n*n+i,t,sum[k]-w[i]);
    				flag&=(sum[k]-w[i]>=0);
    			}
    			if(flag==1&&F.Dinic(s,t)==num)  ans.push_back(k);
    		}
    		for(i=0;i<ans.size();i++)
    		{
    			cout<<ans[i];
    			if(i!=ans.size()-1)  cout<<" ";
    		}
    		cout<<endl;
    	}
    	return 0;
    }
    

\(H\) luogu P5458 [BJOI2016] 水晶

  • 将三维坐标 \((x,y,z)\) 转换成 \((x-z,y-z)\)

  • 两种共振本质相同,都可以写成三个连通的单元使得横纵坐标和 \(\bmod 3\)\(=0,1,2\) 各一次,其中 \(\bmod 3=0\) 的有能量源。

  • 拆点并建出三分图后跑最小割即可。

    点击查看代码
    const int inf=0x3f3f3f3f,dx[6]={1,0,-1,0,-1,1},dy[6]={0,-1,0,1,-1,1};
    map<pair<int,int>,int>c,id;
    map<pair<int,int>,int>::iterator it;
    struct MinCut
    {
    	struct node
    	{
    		int nxt,to,cap,flow;
    	}e[800010];
    	int head[100010],dis[100010],vis[100010],cur[100010],cnt=1;
    	void add(int u,int v,int w)
    	{
    		cnt++;  e[cnt]=(node){head[u],v,w,0};  head[u]=cnt;
    		cnt++;  e[cnt]=(node){head[v],u,0,0};  head[v]=cnt;
    	}
    	bool bfs(int s,int t)
    	{
    		memset(vis,0,sizeof(vis));
    		queue<int>q;
    		dis[s]=1;  cur[s]=head[s];
    		q.push(s);  vis[s]=1;
    		while(q.empty()==0)
    		{
    			int x=q.front();  q.pop();
    			for(int i=head[x];i!=0;i=e[i].nxt)
    			{
    				if(vis[e[i].to]==0&&e[i].cap>e[i].flow)
    				{
    					dis[e[i].to]=dis[x]+1;  cur[e[i].to]=head[e[i].to];
    					q.push(e[i].to);  vis[e[i].to]=1;
    					if(e[i].to==t)  return true;
    				}
    			}
    		}
    		return false;
    	}
    	int dfs(int x,int t,int flow)
    	{
    		if(x==t)  return flow;
    		int sum=0,tmp;
    		for(int i=cur[x];i!=0&&sum<flow;i=e[i].nxt)
    		{
    			cur[x]=i;
    			if(dis[e[i].to]==dis[x]+1&&e[i].cap>e[i].flow)
    			{
    				tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum));
    				if(tmp==0)  dis[e[i].to]=0;
    				sum+=tmp;
    				e[i].flow+=tmp;  e[i^1].flow-=tmp;
    			}
    		}
    		return sum;
    	}
    	int Dinic(int s,int t)
    	{
    		int flow=0;
    		while(bfs(s,t)==true)  flow+=dfs(s,t,inf);
    		return flow;
    	}
    }C;
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,s,t,tot,x,y,z,_x,_y,w,sum=0,i;
    	cin>>n;  s=1;  tot=t=2;
    	for(i=1;i<=n;i++)
    	{
    		cin>>x>>y>>z>>w;  x-=z;  y-=z;
    		w*=(10+(((x+y)%3+3)%3==0));
    		c[make_pair(x,y)]+=w;  sum+=w;
    	}
    	for(it=c.begin();it!=c.end();it++)
    	{
    		tot++;  id[it->first]=tot;
    		tot++;  C.add(id[it->first],tot,it->second);
    	}
    	for(it=c.begin();it!=c.end();it++)
    	{
    		x=it->first.first;  y=it->first.second;
    		if(((x+y)%3+3)%3==1)  C.add(id[it->first]+1,t,inf);
    		else
    		{
    			if(((x+y)%3+3)%3==2)  C.add(s,id[it->first],inf);
    			for(i=0;i<=5;i++)
    			{
    				_x=x+dx[i];  _y=y+dy[i];
    				if(c.find(make_pair(_x,_y))!=c.end()&&((x+y)%3+3+1)%3==((_x+_y)%3+3)%3)
    					C.add(id[it->first]+1,id[make_pair(_x,_y)],inf);
    			}
    		}
    	}
    	printf("%.1lf\n",(sum-C.Dinic(s,t))/10.0);
    	return 0;
    }
    

\(I\) CF724E Goods transportation

\(J\) CF1288F Red-Blue Graph

\(K\) luogu P2805 [NOI2009] 植物大战僵尸

\(L\) CF1404E Bricks

\(M\) luogu P8291 [省选联考 2022] 学术社区

\(N\) luogu P4076 [SDOI2016] 墙上的句子

\(O\) luogu P5470 [NOI2019] 序列

\(P\) luogu P4066 [SHOI2003] 吃豆豆

  • 两条路径不能互相穿过很诈骗。拆成入点和出点后从源点找 \(2\) 条路径即可。

  • 需要精细实现一下连边。具体地,对于形如 \(i \to j \to k\) 的路径,我们只保留 \(i \to j,j \to k\) 两条路径而不保留 \(i \to k\) ,即只与右侧必须要走的点连边。

    点击查看代码
    const int inf=0x3f3f3f3f;
    pair<int,int>a[2010];
    struct MaxFlowMaxCost
    {
    	struct node
    	{
    		int nxt,to,w,cap,flow;
    	}e[750010];
    	int head[4010],dis[4010],vis[4010],cur[4010],cnt=1,cost;
    	void add(int u,int v,int w,int _w)
    	{
    		cnt++;  e[cnt]=(node){head[u],v,_w,w,0};  head[u]=cnt;
    		cnt++;  e[cnt]=(node){head[v],u,-_w,0,0};  head[v]=cnt;
    	}
    	bool spfa(int s,int t)
    	{
    		memset(vis,0,sizeof(vis));
    		memset(dis,-0x3f,sizeof(dis));
    		queue<int>q;
    		dis[s]=0;  cur[s]=head[s];
    		q.push(s);  vis[s]=1;
    		while(q.empty()==0)
    		{
    			int x=q.front();  q.pop();
    			vis[x]=0;
    			for(int i=head[x];i!=0;i=e[i].nxt)
    			{
    				if(dis[e[i].to]<dis[x]+e[i].w&&e[i].cap>e[i].flow)
    				{
    					dis[e[i].to]=dis[x]+e[i].w;  cur[e[i].to]=head[e[i].to];
    					if(vis[e[i].to]==0)
    					{
    						q.push(e[i].to);  vis[e[i].to]=1;
    					}
    				}
    			}
    		}
    		return dis[t]>0;
    	}
    	int dfs(int x,int t,int flow)
    	{
    		if(x==t)  return flow;
    		vis[x]=1;
    		int sum=0,tmp;
    		for(int i=cur[x];i!=0&&sum<flow;i=e[i].nxt)
    		{
    			cur[x]=i;
    			if(vis[e[i].to]==0&&dis[e[i].to]==dis[x]+e[i].w&&e[i].cap>e[i].flow)
    			{
    				tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum));
    				sum+=tmp;
    				e[i].flow+=tmp;  e[i^1].flow-=tmp;
    				cost+=tmp*e[i].w;
    			}
    		}
    		vis[x]=0;
    		return sum;
    	}
    	int Dinic(int s,int t)
    	{
    		cost=0;
    		while(spfa(s,t)==true)  while(dfs(s,t,inf)!=0);
    		return cost;
    	}
    }C;
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,minn,s,_s,t,i,j;
    	cin>>n;  _s=2*n+1;  s=2*n+2;  t=2*n+3; 
    	for(i=1;i<=n;i++)	cin>>a[i].first>>a[i].second;
    	sort(a+1,a+1+n);
    	for(i=1;i<=n;i++)
    	{
    		C.add(s,i,inf,0);  C.add(i+n,t,inf,0);
    		C.add(i,i+n,1,1);  C.add(i,i+n,2,0);
    		minn=inf;
    		for(j=i+1;j<=n;j++)
    		{	
    			if(a[j].second>=a[i].second&&a[j].second<minn)
    			{
    				C.add(i+n,j,2,0);
    				minn=a[j].second;
    			}
    		}
    	}
    	C.add(_s,s,2,0);
    	cout<<C.Dinic(_s,t)<<endl;
    	return 0;	
    }
    

\(Q\) luogu P4553 80人环游世界

\(R\) luogu P3980 [NOI2008] 志愿者招募

\(S\) luogu P6122 [NEERC 2016] Mole Tunnels

\(T\) luogu P5331 [SNOI2019] 通信

  • 将一个点 \(i\) 拆成两个点 \(i,i'\) ,分别用于直接连接到控制中心和被后面某个哨站连接。

  • \(s\)\(i\) 连一条 \(c=1,w=0\) 的边,从 \(i\)\(t\) 连一条 \(c=1,w=W\) 的边,从 \(i'\)\(t\) 连一条 \(c=1,w=0\) 的边,从 \(i\)\(j'(j<i)\) 连一条 \(c=1,w=|a_{i}-a_{j}|\) 的边。取到最大流时合法。

  • 暴力连边的边数是 \(O(n^{2})\) 的,需要进一步优化。

  • 考虑分治优化建图,具体地,将 \([l,r]\) 内的 \(a_{i}\) 升序排序后得到一条虚链, \([mid+1,r]\) 内的点向虚链连边,虚链上的点向 \([l,mid]\) 内的点连边,此时边数缩小到了 \(O(n \log n)\) ,可以接受。

    点击查看代码
    const ll inf=0x3f3f3f3f3f3f3f3f;
    ll a[1010],b[1010],id[2][1010],s,t,tot;
    struct MaxFlowMinCost
    {
    	struct node
    	{
    		ll nxt,to,w,cap,flow;
    	}e[5000010];
    	ll head[13010],dis[13010],vis[13010],cur[13010],cnt=1,cost;
    	void add(ll u,ll v,ll w,ll _w)
    	{
    		cnt++;  e[cnt]=(node){head[u],v,_w,w,0};  head[u]=cnt;
    		cnt++;  e[cnt]=(node){head[v],u,-_w,0,0};  head[v]=cnt;
    	}
    	bool bfs(ll s,ll t)
    	{
    		memset(vis,0,sizeof(vis));
    		memset(dis,0x3f,sizeof(dis));
    		queue<ll>q;
    		dis[s]=0;  cur[s]=head[s];
    		q.push(s);  vis[s]=1;
    		while(q.empty()==0)
    		{
    			ll x=q.front();  q.pop();
    			vis[x]=0;
    			for(ll i=head[x];i!=0;i=e[i].nxt)
    			{
    				if(dis[e[i].to]>dis[x]+e[i].w&&e[i].cap>e[i].flow)
    				{
    					dis[e[i].to]=dis[x]+e[i].w;  cur[e[i].to]=head[e[i].to];
    					q.push(e[i].to);  vis[e[i].to]=1;
    				}
    			}
    		}
    		return dis[t]<inf;
    	}
    	ll dfs(ll x,ll t,ll flow)
    	{
    		if(x==t)  return flow;
    		vis[x]=1;
    		ll sum=0,tmp;
    		for(ll i=cur[x];i!=0&&sum<flow;i=e[i].nxt)
    		{
    			cur[x]=i;
    			if(vis[e[i].to]==0&&dis[e[i].to]==dis[x]+e[i].w&&e[i].cap>e[i].flow)
    			{
    				tmp=dfs(e[i].to,t,min(e[i].cap-e[i].flow,flow-sum));
    				sum+=tmp;
    				e[i].flow+=tmp;  e[i^1].flow-=tmp;
    				cost+=tmp*e[i].w;
    			}
    		}
    		vis[x]=0;
    		return sum;
    	}
    	ll Dinic(ll s,ll t)
    	{
    		cost=0;
    		while(bfs(s,t)==true)  while(dfs(s,t,inf)!=0);
    		return cost;
    	}
    }F;
    void solve(ll l,ll r)
    {
    	if(l==r)  return;
    	ll mid=(l+r)/2;
    	solve(l,mid);  solve(mid+1,r);
    	b[0]=0;
    	for(ll i=l;i<=r;i++)
    	{
    		b[0]++;  b[b[0]]=a[i];
    	}
    	sort(b+1,b+1+b[0]);  b[0]=unique(b+1,b+1+b[0])-(b+1);
    	for(ll i=1;i<=b[0]-1;i++)
    	{
    		F.add(tot+i,tot+i+1,inf,b[i+1]-b[i]);
    		F.add(tot+i+1,tot+i,inf,b[i+1]-b[i]);
    	}	
    	for(ll i=l;i<=r;i++)
    	{
    		ll pos=lower_bound(b+1,b+1+b[0],a[i])-b;
    		if(i<=mid)  F.add(tot+pos,id[1][i],1,0);
    		else  F.add(id[0][i],tot+pos,1,0);
    	}
    	tot+=b[0];
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	ll n,w,i;
    	cin>>n>>w;  s=1;  tot=t=2;
    	for(i=1;i<=n;i++)  
    	{
    		cin>>a[i];
    		tot++;  id[0][i]=tot;
    		tot++;  id[1][i]=tot;
    		F.add(s,id[0][i],1,0);  F.add(id[0][i],t,1,w);  F.add(id[1][i],t,1,0);
    	}
    	solve(1,n);
    	cout<<F.Dinic(s,t)<<endl;
    	return 0;
    }
    

\(U\) luogu P6621 [省选联考 2020 A 卷] 魔法商店

\(V\) luogu P2304 [NOI2015] 小园丁与老司机

posted @ 2025-02-14 17:51  hzoi_Shadow  阅读(248)  评论(5)    收藏  举报
扩大
缩小