能力提升综合题单 Part 8.9.1 最大流

1.P3376 【模板】网络最大流

#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;
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<=n;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;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&s,&t);
	int u,v,w;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		add_edge(u,v,w);
	}
	printf("%d",dinic());
	return 0;
}

2.P4722 【模板】最大流 加强版 / 预流推进

鬼畜的HLPP,之后补板子

3.P2065 [TJOI2011]卡片

这题是个蓝题我觉得是不合理的,比后面的紫题没得简单
暴力 g c d gcd gcd会直接 T L E TLE TLE
先跑一遍欧拉筛
对于红蓝卡片的每一个数,枚举与这些数不互质的数 i i i,红牌向 i i i连边,蓝牌被 i i i连边,然后
从源点向红牌连边,蓝牌向汇点连边,求出最大流即可

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e4+5,maxm=1e4+5;
const int MAX=0x3f3f3f3f;
int cur[maxn],head[maxn],dep[maxn];
int a[maxn],b[maxn];
int cnt=1,maxflow=0,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));
    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(){
	while(bfs()){
		maxflow+=dfs(s,inf);
	}
	return maxflow;
}
inline void init(){
	memset(head,0,sizeof head);
	maxflow=0;
	cnt=1;
}
int k,c;
const int maxnn=1e7+10;
int prime[2000005];//存素数 
bool sf[maxnn];//判断这个数是否是素数 
int num=0;
void sushu(){                
    memset(sf,true,sizeof(sf));
    for(int i=2;i<=maxnn;i++){          
        if(sf[i]) prime[++num]=i;     
        for(int j=1;j<=num;j++){     
            if(i*prime[j]>maxnn) break; 
            sf[i*prime[j]]=false;      
            if(i%prime[j]==0) break;   
        }
    }
    sf[1]=false;
    sf[0]=false;
}
inline void link1(int x,int id){
    for(int i=1;i<=num&&prime[i]<=x;i++){
        if(x%prime[i]==0){
            add_edge(id,i+n+m,MAX);
            t=max(t,i+n+m);
            while(x%prime[i]==0)x/=prime[i];
        }
    }
}
inline void link2(int y,int id){
    for(int i=1;i<=num&&prime[i]<=y;i++){
        if(y%prime[i]==0){
            add_edge(i+n+m,id+n,MAX);
            t=max(t,i+n+m);
            while(y%prime[i]==0)y/=prime[i];
        }
    }
}
int main(){
    int tt;
	cin>>tt;
	sushu();
	while(tt--){
		scanf("%d%d",&n,&m);
		init();
		t=0;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			link1(a[i],i);
		}
		for(int i=1;i<=m;i++){
			scanf("%d",&b[i]);
			link2(b[i],i);
		}
		s=0;
		t+=1;
		for(int i=1;i<=n;i++){
			add_edge(0,i,1);
		}
		for(int i=1;i<=m;i++){
			add_edge(i+n,t,1);
		}
		printf("%d\n",dinic());
	} 
}

4.P2763 试题库问题

直接匹配或者最大流都可, 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],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;
}

5.P2472 [SCOI2007]蜥蜴

看到点限制的题第一时间想到拆点!拆点!拆点!重要的事情说三遍
枚举每一个能跳出去的点连向汇点
枚举每一个可到达的点周围可以跳到的可到达点
最大流

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1005,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],to[maxn],flag[maxn];
int cnt=1,n,m,s,t,k,tot,l,r,d;
char a[maxn][maxn],h[maxn][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(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 js(int i,int j){
	return (i-1)*r+j;
}
inline int juli(int x1,int y1,int x2,int y2){
	return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
int main(){
	cin>>l>>r>>d;
	n=r*l;
	s=0,t=l*r*2+1;
	for(int i=1;i<=l;i++){
		for(int j=1;j<=r;j++){
			cin>>h[i][j];
			if(h[i][j]-'0'>0)add_edge(js(i,j),n+js(i,j),h[i][j]-'0');
		}
	}
	int watot=0;
	for(int i=1;i<=l;i++){
		for(int j=1;j<=r;j++){
			cin>>a[i][j];
			if(a[i][j]=='L'){
				watot++;
				add_edge(s,js(i,j),1);
			}
		}
	}
	for(int i=1;i<=l;i++){
		for(int j=1;j<=r;j++){
			int x1=i,y1=j;
			if(x1-d<1||y1-d<1||x1+d>l||y1+d>r){
				add_edge(js(x1,y1)+n,t,inf);
			}
			for(int x2=1;x2<=l;x2++){
				for(int y2=1;y2<=r;y2++){
					if(juli(x1,y1,x2,y2)<=d*d){
						add_edge(js(x1,y1)+n,js(x2,y2),inf);
					}
				}
			}
		}
	}
	printf("%d",watot-dinic());
	return 0;
}

6.P2754 [CTSC1999]家园 / 星际转移问题

这个题很是骚

分层图网络流

每过一天建一套站点,借用一下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;
}

7.P2765 魔术球问题

做这题之前我先做了最小路径覆盖问题

然后读懂题目会发现,其实这好像是一样的意思?只不过这题是枚举 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;
}

8.P2766 最长不下降子序列问题

  1. 计算其最长不下降子序列的长度 s s s
  2. 如果每个元素只允许使用一次,计算从给定的序列中最多可取出多少个长度为 s s s 的不下降子序列。
  3. 如果允许在取出的序列中多次使用 x 1 x_1 x1 x n x_n xn(其他元素仍然只允许使用一次),则从给定序列中最多可取出多少个不同的长度为 s s s 的不下降子序列。

a 1 , a 2 , … , a s a_1, a_2, \ldots, a_s a1,a2,,as 为构造 S S S 时所使用的下标, b 1 , b 2 , … , b s b_1, b_2, \ldots, b_s b1,b2,,bs 为构造 T T T 时所使用的下标。且 ∀ i ∈ [ 1 , s − 1 ] \forall i \in [1,s-1] i[1,s1],都有 a i < a i + 1 a_i < a_{i+1} ai<ai+1 b i < b i + 1 b_i <b_{i+1} bi<bi+1。则 S S S T T T 不同,当且仅当 ∃ i ∈ [ 1 , s ] \exists i \in [1,s] i[1,s],使得 a i ≠ b i a_i \neq b_i ai=bi

第一问:求 L I S LIS LIS

拆点

第二问:源点连一条到 d p [ i ] = = 1 dp[i]==1 dp[i]==1点的边, d [ j ] = = s d[j]==s d[j]==s连一条到汇点的边,对于i枚举每一个j, j < i j<i j<i如果 a [ j ] < = a [ i ] a[j]<=a[i] a[j]<=a[i]&& d p [ j ] + 1 = = d p [ i ] dp[j]+1==dp[i] dp[j]+1==dp[i], j j j的出点向 i i i的入点连一条边(以上容量均为1)

第三问:把 1 1 1 n n n点的拆点容量设为 i n f inf inf

最后,要特判!!!不然 n = 1 n=1 n=1就会炸掉

#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;
}

xx.最大权闭合子图:P4174 [NOI2006]最大获利

引用一下大佬对最大权闭合子图的定义

什么是最大权闭合子图:

先解释一下有向图的闭合图:闭合图内任意点的任意后继也一定还在闭合图中。

物理意义:事物间依赖关系:一个事件要发生,它需要的所有前提也都一定要发生。

最大权闭合图:点权之和最大的闭合图

最大权闭合图构图方法:

1.增加源s汇t

2.源s连接原图的正权点,容量为相应点权

3.原图的负权点连接汇t,容量为相应点权的相反数

4.原图边的容量为正无限。

最大权闭合图 解决:

闭合图V的权为正权点总和减去对应割的容量

当割最小时,闭合图权最大

画个图:(作图网站炸了,用luogu的图)
在这里插入图片描述
我最这个模型的理解就是,和源点相连的点全都是收益,和汇点相连的点为代价,割点与源点相连的边代表选择这个收益,割掉与汇点相连的边代表选择付出这个代价

最后 s u m − sum- sum最小割即为总最大总收益

我们考虑一下这个图,一开始当然是想所有的收益都选,然后我们发现选择4和5的话,代价会比收益更大,所以我们不选,即割掉源点与4,5相连的边,就不用付出4,5到汇点的代价了,选择1,2,3的收益,就要选择付出1,2,3到汇点的代价,割之。

最终我们舍弃了4,5这两个代价比收益还要大的点,并付出了要选择1,2,3的收益需要付出的代价
这就是 s u m − sum- sum最大流 的来由。

#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;
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;
}
int main(){
	scanf("%d%d",&n,&m);
	s=0,t=n+m+1;
	int u,v,w;
	int x;
	for(int i=1;i<=n;i++){
		scanf("%d",&x);
		add_edge(i+m,t,x);
	}
	int sum=0;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		add_edge(s,i,w);
		sum+=w;
		add_edge(i,m+u,inf);
		add_edge(i,m+v,inf);
	}
	printf("%d",sum-dinic());
	return 0;
}

9.P2805 [NOI2009]植物大战僵尸

其实我觉得这题和后面一题都应该放在最小割里面

看到有收益有代价,并且有选点有先决条件,这时候想到:

最大权闭合子图

有个特殊问题是如果两个植物他可以互保,那他们以及后面的所有植物都无法被吃,所以我们需要通过
拓扑排序来排除这些点

每个点向被他保护的点连边,进行拓扑排序

模拟一下就能知道,因为拓扑排序过程中要入度为0才会被加入队列,所以入度 > = 2 >=2 >=2的点以及后面的点就不会被取出,我们把所有取出的点用mark标记

如果收益为正,从源点向它连边,容量为负,从它向汇点连边

每一个点对它后面的点连一条容量为 i n f inf inf的边

求出 s u m − sum- sum最小割即为答案

#include<bits/stdc++.h>
using namespace std;
const int inf =0x3f3f3f3f,maxn=1e5+5,maxm=4e5+5;
int cur[maxn],head[maxn],dep[maxn],degree[maxn];
int cnt=1,n,m,s,t,cc=2;
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 mark[maxn];
vector<int>g[maxn];
int f[maxn],sum;
inline void topo(){
	queue<int>q;
	for(int i=1;i<=m*n;i++){
		if(!degree[i])q.push(i);
	}
	while(!q.empty()){
		int u=q.front();
		q.pop();
		mark[u]=1;
		for(auto v:g[u]){
			if(!(--degree[v])){
				q.push(v);
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	int u,v,w,x,y,num;
	s=n*m+1,t=n*m+2;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			scanf("%d%d",&f[id(i,j)],&num);
			if(j!=m){
		    	g[id(i,j+1)].push_back(id(i,j));
	    		degree[id(i,j)]++;
	    	}
	    	while(num--){
	    		scanf("%d%d",&x,&y);
	    		g[id(i,j)].push_back(id(x+1,y+1));
	    		degree[id(x+1,y+1)]++;
			}
		}
	}
	topo();
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(mark[id(i,j)]){
				int c=f[id(i,j)];
				if(c>0){
					sum+=c;
					add_edge(s,id(i,j),c);
				}
				else if(c<0)add_edge(id(i,j),t,-c);
				for(auto v:g[id(i,j)]){
					if(mark[v]){
						add_edge(v,id(i,j),inf);
					}
				}
			}
		}
	}
	printf("%d",sum-dinic());
	return 0;	
}

10.P3749 [六省联考2017]寿司餐厅

写到这里我感觉需要把最大权闭合子图的模板填上来一下

每一个收益视为一个点,而必须选择被这个区间包含的区间

每个点向种类连边容量为 i n f inf inf

每个点向他区间包含的点连边:

a d d add add_ e d g e ( i d [ i ] [ j ] , i d [ i ] [ j − 1 ] , i n f ) edge(id[i][j],id[i][j-1],inf) edge(id[i][j],id[i][j1],inf);

a d d add add_ e d g e ( i d [ i ] [ j ] , i d [ i + 1 ] [ j ] , i n f ) edge(id[i][j],id[i+1][j],inf) edge(id[i][j],id[i+1][j],inf);

并向源点or汇点连边:
i f ( c > 0 ) if(c>0) if(c>0){
a d d add add e d g e ( s , i d [ i ] [ j ] , c ) edge(s,id[i][j],c) edge(s,id[i][j],c);
s u m + = c sum+=c sum+=c;
}
e l s e i f ( c < 0 ) else if(c<0) elseif(c<0){
a d d add add
e d g e ( i d [ i ] [ j ] , t , − c ) edge(id[i][j],t,-c) edge(id[i][j],t,c);
}

每个 i = = j i==j i==j的点向种类点连边:

a d d add add_ e d g e ( i d [ i ] [ j ] , c c + a [ i ] , i n f ) edge(id[i][j],cc+a[i],inf) edge(id[i][j],cc+a[i],inf);

最终我们还要处理费用,如果选择过这个种类,则向连接一条 i ∗ i i*i ii的边

每次选择这个种类的代价在上面加边的过程中减去即可

做出图来是这个鬼畜的样子(因为我没把种类点分开)
在这里插入图片描述
完整代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int inf =0x3f3f3f3f,maxn=10005,maxm=1e6+5;
int cur[maxn],head[maxn],dep[maxn];
int cnt=1,n,m,s,t,cc;
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<=cc+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;
}
int a[105],f[105][105],id[105][105],sum=0;
signed main(){
	cc=2;
	s=1,t=2;
	int maxx=0;
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		maxx=max(maxx,a[i]);
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			scanf("%lld",&f[i][j]);
			id[i][j]=++cc;
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=i;j<=n;j++){
			int c=f[i][j];
			if(i==j){
				if(m)add_edge(id[i][j],cc+a[i],inf);
//				printf("%d %d+%d inf\n",id[i][j],cc,a[i]);
				c-=a[i];
			}
			else{
				add_edge(id[i][j],id[i][j-1],inf);
				add_edge(id[i][j],id[i+1][j],inf);
//				printf("%d %d inf\n",id[i][j],id[i][j-1]);
//				printf("%d %d inf\n",id[i][j],id[i+1][j]);
			}
			if(c>0){
				add_edge(s,id[i][j],c);
				sum+=c;
//				printf("s %d %d\n",id[i][j],c);
			}
			else if(c<0){
				add_edge(id[i][j],t,-c);
//				printf("%d t %d\n",id[i][j],-c);
			}
		}
	}
	if(m){
		for(int i=1;i<=maxx;i++){
			add_edge(++cc,t,i*i);
//			printf("%d t %d\n",cc,i*i);
		}
	}
	printf("%lld",sum-dinic());
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值