20230812-网络流 I

20230812

最小路径覆盖

考虑把每一个点拆成两个点:入点和出点
对于每一条边 \(u \to v\) ,我们把 \(u\) 的出点与 \(v\) 的入点相连
再把 \(st\) 与所有点的出点相连,\(ed\) 与所有点的入点相连
这样跑一遍最大流 \(dinic()\),答案就是 \(n-flow\)

P2764 最小路径覆盖问题

题目描述

传送门
给定一张DAG,要求用最少的路径覆盖整张图。数据范围:\(n, m \le 100\)

Solution

经典问题
考虑把每一个点拆成两个点:入点和出点
对于每一条边 \(u \to v\) ,我们把 \(u\) 的出点与 \(v\) 的入点相连
再把 \(st\) 与所有点的出点相连,\(ed\) 与所有点的入点相连
这样跑一遍最大流 \(dinic()\),答案就是 \(n-flow\)

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int N=6e3+5,inf=0x3f3f3f3f;
int n,m,head[N],tot=0,ans=0,lv[N],st,ed,cur[N],fa[N],cnt=0,vis[N];
struct edge{
  int u,v,nxt,val;
}e[N<<1];

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

void add(int u,int v){
  e[++tot]=(edge){u,v,head[u],1};
  head[u]=tot;
  e[++tot]=(edge){v,u,head[v],0};
  head[v]=tot;
}

bool bfs(){
  for(int i=0;i<=n*2+1;i++) lv[i]=-1;
  queue<int> q;
  q.push(st);
  lv[st]=1;
  cur[st]=head[st];
  while(!q.empty()){
  	int u=q.front();q.pop();
  	for(int i=head[u];i!=-1;i=e[i].nxt){
  	  int v=e[i].v,val=e[i].val;
	  if(val>0&&lv[v]==-1){
	  	q.push(v);;
	  	cur[v]=head[v];
	  	lv[v]=lv[u]+1;
	  }	
	}
  }
  return lv[ed]!=-1;
}

int dfs(int u,int flow){
  if(u==ed) return flow;
  int res=flow;
  for(int i=cur[u];i!=-1;i=e[i].nxt){
  	cur[u]=i;
  	int v=e[i].v,val=e[i].val;
  	if(lv[v]==lv[u]+1&&val>0){
  	  int c=dfs(v,min(val,res));
	  res-=c;
	  e[i].val-=c;
	  e[i^1].val+=c;	
	}
  }
  return flow-res;
}

int dinic(){
  int res=0;
  while(bfs()) res+=dfs(st,inf);
  return res;
}

int find(int x){
  if(fa[x]==x) return x;
  return fa[x]=find(fa[x]);
}

void marge(int u,int v){
  u=find(u);v=find(v);
  if(u!=v) fa[u]=v;
}
vector<vector<int> > g(N); 
void work(){
  for(int i=1;i<=n;i++) fa[i]=i;
  for(int i=0,u,v;i<=tot;i+=2){
  	if(e[i].val==1||e[i].u==st||e[i].v==ed) continue;
  	u=e[i].u;v=e[i].v;
  	if(u>n) u-=n;
  	if(v>n) v-=n;
  	marge(u,v);
  }
  cnt=0;
  for(int i=1;i<=n;i++){
  	if(!vis[find(i)]) vis[fa[i]]=++cnt;
  	g[vis[fa[i]]].push_back(i);
  }
  for(int i=1;i<=cnt;i++){
    for(int j=0;j<g[i].size();j++)
      printf("%d ",g[i][j]); 
	printf("\n"); 	
  }
}

int main(){
  /*2023.8.19 H_W_Y P2764 最小路径覆盖问题 网络最大流*/ 
  n=read();m=read();
  st=0;ed=n*2+1;
  memset(head,-1,sizeof(head));tot=-1;
  for(int i=1;i<=n;i++) add(st,i),add(i+n,ed);
  for(int i=1,u,v;i<=m;i++){
    u=read();v=read();
    add(u,v+n);
  } 
  ans=n-dinic();
  work();
  printf("%d\n",ans);
  return 0;
}

P3254 圆桌问题

题目描述

传送门
有来自 \(m\) 个不同单位的代表参加一次国际会议。
\(i\) 个单位派出了 \(r_i\) 个代表。 会议的餐厅共有 \(n\) 张餐桌,第 \(i\) 张餐桌可容纳 \(c_i\) 个代表就餐。
为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。
请给出一个满足要求的代表就餐方案。
\(1 \le m \le 150,1 \le n \le 270,1 \le r_i, c_i \le 10^3\)

Solution

简单易懂
建立一个二分图,左部点是单位,右部点是餐桌。
\(s\) 向每个单位连一条容量为 \(r_i\) 的边,表示代表个数的限制。
从每个单位向每个餐桌连一条容量为 \(1\) 的边,表示每个餐桌相同单位至多一个人。
从每个餐桌向 \(t\) 连一条容量为 \(c_i\) 的边,表示一个餐桌最多坐 \(c_i\) 个人。

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e3+10,INF=0x3f3f3f3f;
int n,m,a[maxn],head[maxn],tot=-1,cur[maxn],lv[maxn],s,t,sum=0;
struct edge{
  int u,v,nxt,val;
}e[maxn*maxn];

void add(int u,int v,int val){
  e[++tot]=(edge){u,v,head[u],val};
  head[u]=tot;
  e[++tot]=(edge){v,u,head[v],0};
  head[v]=tot;
}

bool bfs(){
  memset(lv,-1,sizeof(lv));
  queue<int> q;
  q.push(s);
  cur[s]=head[s];
  lv[s]=1;
  while(!q.empty()){
    int now=q.front();q.pop();
    for(int i=head[now];i!=-1;i=e[i].nxt){
      int v=e[i].v,val=e[i].val;
      if(lv[v]==-1&&val>0){
      	q.push(v);
      	cur[v]=head[v];
      	lv[v]=lv[now]+1;
	  }
	}
  }
  return lv[t]!=-1; 
}

int dfs(int now,int flow){
  if(now==t) return flow;
  int res=flow;
  for(int i=cur[now];i!=-1;i=e[i].nxt){
  	cur[now]=i;
  	int v=e[i].v,val=e[i].val;
  	if(val>0&&lv[v]==lv[now]+1){
  	  int c=dfs(v,min(res,val));
      res-=c;
	  e[i].val-=c;
	  e[i^1].val+=c;
	}
  }
  return flow-res;
}

int dinic(){
  int ans=0;
  while(bfs()) ans+=dfs(0,INF);
  return ans;
}

int main(){
  /*2023.2.25 hewanying P3254 [网络流24题]圆桌问题 网络最大流*/ 
  scanf("%d%d",&m,&n);
  s=0,t=n+m+2;
  memset(head,-1,sizeof(head));
  for(int i=1;i<=m;i++){
  	scanf("%d",&a[i]);
  	add(s,i,a[i]);
  	sum+=a[i];
  }
  for(int i=m+1;i<=n+m;i++){
  	scanf("%d",&a[i]);
  	add(i,t,a[i]);
  	for(int j=1;j<=m;j++) add(j,i,1);
  }
  if(dinic()==sum){
  	printf("1\n");
  	for(int i=1;i<=m;i++){
  	  for(int j=head[i];j!=-1;j=e[j].nxt)
  	    if(!(j&1)&&e[j].val==0) printf("%d ",e[j].v-m);  
	  printf("\n");		
    }
  }
  else printf("0\n");
  
  return 0;
}

P2472 [SCOI2007] 蜥蜴

题目描述

传送门
一个 \(n \times m\) 的网格中,有一些格子有石柱,石柱的高度为 \(1 \sim 3\)
有一些石柱的顶上有蜥蜴,蜥蜴每次可以跳到距离不超过 \(d\) 的石柱上,或者跳到界外。
蜥蜴跳一次, 它所在的石柱的高度就减一,如果某个石柱的高度为 \(0\) 了,石柱就消失,以后蜥蜴不能跳到这里。现在要使得剩下无法逃脱的蜥蜴数最少。
\(1 \le r, c \le 20, 1 \le d \le 4\)

Solution

简单易懂
将每个石柱拆成两个点,之间连容量为高度 \(h\) 的边,表示对经过这个石柱的蜥蜴数量限制。

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int maxn=1e3+10,INF=0x3f3f3f3f;
int n,m,d,lv[maxn],s,t,head[maxn],tot=-1,x,cur[maxn],a[maxn][maxn],sum=0;
string st;
struct edge{
  int u,v,nxt,val;
}e[maxn*maxn];

void add(int u,int v,int val){
  e[++tot]=(edge){u,v,head[u],val};
  head[u]=tot; 
  e[++tot]=(edge){v,u,head[v],0};
  head[v]=tot;
}

bool bfs(){
  memset(lv,-1,sizeof(lv));
  lv[s]=0;cur[s]=head[s];
  queue<int> q;
  q.push(s);
  while(!q.empty()){
    int now=q.front();q.pop();
    for(int i=head[now];i!=-1;i=e[i].nxt){
      int v=e[i].v,val=e[i].val;
      if(val>0&&lv[v]==-1){
      	lv[v]=lv[now]+1;
        cur[v]=head[v];
        q.push(v);
	  }
	}
  }
  return lv[t]!=-1; 
}

int dfs(int now,int flow){
  if(now==t) return flow;
  int res=flow;
  for(int i=cur[now];i!=-1;i=e[i].nxt) {
  	cur[now]=i;
  	int v=e[i].v,val=e[i].val;
  	if(val>0&&lv[v]==lv[now]+1){
  	  int c=dfs(v,min(res,val));
	  res-=c;
	  e[i].val-=c;
	  e[i^1].val+=c;	
	}
  }
  return flow-res;
}

int dinic(){
  int ans=0;
  while(bfs()) ans+=dfs(s,INF);
  return ans;
}

int main(){
  /*2023.3.4 hewanying P2472 蜥蜴 网络最大流*/ 
  memset(head,-1,sizeof(head));tot=-1;
  scanf("%d%d%d",&n,&m,&d);
  s=0;t=2*n*m+1;
  for(int i=1;i<=n;i++){
  	cin>>st;
    for(int j=1;j<=m;j++){
  	  a[i][j]=x=st[j-1]-'0';
  	  if(x==0) continue;
  	  add(2*(n*(i-1)+j)-1,2*(n*(i-1)+j),x);
  	  if(i<=d||n-i<d||j<=d||m-j<d) add(2*(n*(i-1)+j),t,INF);
  	  for(int k=1;k<=i;k++)
  	    for(int k2=1;k2<=m;k2++){
  	      if(k==i&&k2==j) break;
		  if((k-i)*(k-i)+(j-k2)*(j-k2)<=d*d&&a[k][k2]>0){
		  	add(2*(n*(k-1)+k2),2*(n*(i-1)+j)-1,INF); 
		  	add(2*(n*(i-1)+j),2*(n*(k-1)+k2)-1,INF); 
		  }	
		}
    }  	
  }

  for(int i=1;i<=n;i++){
  	cin>>st;
  	for(int j=0;j<m;j++)
  	  if(st[j]=='L') sum++,add(s,2*(n*(i-1)+j+1)-1,1);
  }
  printf("%d\n",sum-dinic());
  return 0;
}

P2765 魔术球问题

题目描述

传送门
假设有 \(n\) 根柱子,现要按下述规则在这 \(n\) 根柱子中依次放入编号为 \(1, 2, 3, 4, \dots\) 的 球。

  1. 每次只能在某根柱子的最上面放球。
  2. 在同一根柱子中,任何 \(2\) 个相邻球的编号之和为完全平方数。
    试设计一个算法,计算出在 \(n\) 根柱子上最多能放多少个球。
    \(1 \le n \le 55\)

Solution

发现如果对于前面的 \(m\) 个数
那么我们可以把这 \(m\) 个数按照可以相邻的关系建立一个图
在这个图上跑出最小路径覆盖
如果最小路径覆盖 \(\le n\)
\(m\) 个数是成立的

这样我们考虑去枚举 \(m\)
每一次多一个数就加上与它相关的边
再在残图上面跑网络流即可
所以时间复杂度是可以保证的

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int N=2e4+5,k=1e4,inf=0x3f3f3f3f;
int n,m,head[N],tot=-1,cur[N],lv[N],st,ed,ans,sum,num,sq[N];
struct edge{
  int u,v,nxt,val; 
}e[N<<2];

void add(int u,int v){
  e[++tot]=(edge){u,v,head[u],1};
  head[u]=tot;
  e[++tot]=(edge){v,u,head[v],0};
  head[v]=tot;
}

bool bfs(){
  memset(lv,-1,sizeof(lv));
  lv[st]=1;
  queue<int> q;
  q.push(st);
  cur[st]=head[st];
  while(!q.empty()){
  	int u=q.front();q.pop();
  	for(int i=head[u];i!=-1;i=e[i].nxt){
  	  int v=e[i].v,val=e[i].val;
	  if(val>0&&lv[v]==-1){
	  	lv[v]=lv[u]+1;
	  	q.push(v);
	  	cur[v]=head[v];
	  }	
	}
  }
  return lv[ed]!=-1;
}

int dfs(int u,int flow){
  if(u==ed) return flow;
  int res=flow;
  for(int i=cur[u];i!=-1;i=e[i].nxt){
  	cur[u]=i;
  	int v=e[i].v,val=e[i].val;
  	if(val>0&&lv[v]==lv[u]+1){
  	  int c=dfs(v,min(res,val));
	  e[i].val-=c;
	  res-=c;
	  e[i^1].val+=c;	
	}
  }
  return flow-res;
}

int dinic(){
  int res=0;
  while(bfs()) res+=dfs(st,inf);
  return res;
}

int fa[N],vis[N];
int find(int x){return (fa[x]==x)?x:(fa[x]=find(fa[x]));}
void marge(int u,int v){
  u=find(u);v=find(v);
  if(u!=v) fa[u]=v;
}
vector<vector<int> >g(N);
void work(){
  for(int i=1;i<=m;i++) fa[i]=i;
  for(int i=0;i<=tot;i+=2){
  	if(e[i].u==st||e[i].v==ed||e[i].val==1) continue;
  	int u=((e[i].u>k)?(e[i].u-k):e[i].u),v=((e[i].v>k)?(e[i].v-k):e[i].v);
  	marge(u,v);
  }
  int cnt=0;
  for(int i=1;i<=m;i++){
  	if(!vis[find(i)]) vis[fa[i]]=++cnt;
    g[vis[fa[i]]].push_back(i);
  }
  for(int i=1;i<=cnt;i++){
  	for(int j=0;j<g[i].size();j++)
  	  printf("%d ",g[i][j]);
  	printf("\n");
  }
}

int main(){
  /*2023.8.19 H_W_Y P2765 魔术球问题 网络最大流*/ 
  scanf("%d",&n);
  st=0;ed=20001;m=1;ans=0,sum=0;num=1;
  memset(head,-1,sizeof(head));tot=-1;
  for(int i=1;i<=k;i++) sq[i]=i*i;
  while(1){
  	add(st,m);add(m+k,ed);
  	int c=upper_bound(sq+1,sq+k+1,m)-sq;
  	for(int i=c;sq[i]-m<m;i++) add(sq[i]-m,m+k);
  	ans=dinic();sum+=ans;
  	if(m-sum>n) break;
  	m++;
  }
  m--;
  printf("%d\n",m);
  work();
  return 0;
}

最大点独立集

最大点独立集,顾名思义,要求选出一个点集是每两个点之间都不相连
二分图中,最大点独立集= \(n-\) 二分图最大匹配
很显然可以感受到,简单易懂

CF1404E Bricks

题目描述

你有一个 \(n\times m(1\le n,m\le 200)\)的格子纸,格子要么涂黑(#)要么涂白(.)。你需要用若干个长为一,宽为任意正整数或者宽为一,长为任意正整数的长方形去覆盖所有黑色格子,要求不能盖到白色格子上,不能盖到其他长方形上,不能盖出格子纸的边界,求最少用多少个长方形。

Solution

想了半天没有想通
考虑对于每一条连接两个黑色方块的边,
我们把这两个方块合并就意味着把这一条边擦除
那么我们一定是要让擦除的边越多越好了

但是发现边的选择是有条件的
我们不能同时选择一个黑色方块的横边和竖边
发现这是很显然的一个二分图了
我们把不能一起选的横边和竖边连在一起
那么我们希望擦除的边就是最大点独立集
再用网络流跑二分图最大匹配即可
最后的答案就是 \(sum-(cnt-dinic())\)

H_W_Y-Coding
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5,inf=0x3f3f3f3f;
int a[205][205],n,m,head[N],cnt=0,tot=-1,lv[N],st,ed,cur[N],k,id,tmp,sum=0;
bool flag=false,vis[N];
struct edge{
  int v,nxt,val;
}e[N<<5];

void add(int u,int v){
  e[++tot]=(edge){v,head[u],1};
  head[u]=tot;
  e[++tot]=(edge){u,head[v],0};
  head[v]=tot;
}

bool bfs(){
  memset(lv,-1,sizeof(lv));
  queue<int > q;
  q.push(st);
  lv[st]=1;cur[st]=head[st];
  while(!q.empty()){
  	int u=q.front();q.pop();
  	for(int i=head[u];i!=-1;i=e[i].nxt){
  	  int v=e[i].v,val=e[i].val;
	  if(val>0&&lv[v]==-1){
	  	lv[v]=lv[u]+1;
	  	cur[v]=head[v];
	  	q.push(v);
	  }	
	}
  } 
  return lv[ed]!=-1;
}

int dfs(int u,int flow){
  if(u==ed) return flow;
  int res=flow;
  for(int i=cur[u];i!=-1;i=e[i].nxt){
  	cur[u]=i;
  	int v=e[i].v,val=e[i].val;
  	if(val>0&&lv[v]==lv[u]+1){
  	  int c=dfs(v,min(res,val));
	  res-=c;
	  e[i].val-=c;
	  e[i^1].val+=c;	
	}
  }
  return flow-res;
}

int dinic(){
  int res=0;
  while(bfs()) res+=dfs(st,inf);
  return res;
}

int main(){
  /*2023.8.20 H_W_Y CF1404E Bricks 最大独立点集*/ 
  memset(head,-1,sizeof(head));tot=-1;cnt=0;
  scanf("%d%d",&n,&m);
  k=m*(n-1);
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
      char ch=getchar();
      while(ch!='#'&&ch!='.') ch=getchar();
      if(ch=='#') a[i][j]=1,sum++;
	}
  st=0;ed=2*n*m+1;
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++){
      if(!a[i][j]) continue;
      if(a[i][j-1]){
      	id=k+(i-1)*(m-1)+j-1;
      	vis[id]=true;
      	add(id,ed);
	  }
      if(a[i-1][j]){
      	id=(i-2)*m+j;flag=false;
      	add(st,id);vis[id]=true;
      	if(a[i][j-1]){
      	  tmp=k+(i-1)*(m-1)+j-1;
      	  vis[tmp]=true;
      	  add(id,tmp);
		}
		if(a[i][j+1]){
		  tmp=k+(i-1)*(m-1)+j;
		  vis[tmp]=true;
		  add(id,tmp);
		}
	  }
	  if(a[i+1][j]){
      	id=(i-1)*m+j;flag=false;
      	if(a[i][j-1]){
      	  tmp=k+(i-1)*(m-1)+j-1;
      	  vis[tmp]=true;
      	  add(id,tmp);flag=true;
		}
		if(a[i][j+1]){
		  tmp=k+(i-1)*(m-1)+j;
		  vis[tmp]=true;
		  add(id,tmp);flag=true;
		}
		vis[id]|=flag;	  	
	  }
	}
  for(int i=1;i<=2*n*m;i++)
    if(vis[i])
      cnt++;
  printf("%d\n",sum-(cnt-dinic()));
  return 0;
}

最小割最大流问题

最小割:把图划分成两个子集,所需要割掉的最小容量
感性理解:最小割=最大流

如何求最小割的子集?
\(st\) 出发,沿着 \(val \gt 0\) 的边走下去
能走到的点就是最小割

最大权闭合子图

给定一个有向图,点有点权。
如果一个点 \(u\) 被选了,所有 \(u\) 的出边指向的点 \(v\) 也必须选。
求最大收益。(点权可以为负数)

利用最小割来解决。先假设所有正点权都选。
正点权连到 \(st\),表示放弃这个点,负点权连到 \(ed\),表示选择这个点。
原图中所有 \((u, v)\) 连接一条 \((u, v, \inf)\) 的边。

P2762 太空飞行计划问题

题目描述

\(m\) 个实验,每个实验只可以进行一次,但会获得相应的奖金,有 \(n\) 个仪器,每个实验都需要一定的仪器,每个仪器可以运用于多个实验,但需要一定的价值,问奖金与代价的差的最大值是多少?
\(1 \le n, m \le 50\)

Solution

简单题
差不多是最大权闭合子图问题的模板

H_W_Y-Coding
#include <bits/stdc++.h>
using namespace std;

const int N=1e3+5,inf=0x3f3f3f3f;
int n,m,a[N],head[N],tot=-1,cur[N],lv[N],sum=0,ans=0,st,ed;
bool flag=true;
struct edge{
  int v,nxt,val;
}e[N<<4];

bool read(int &x){
  x=0;
  int f=1;char ch=getchar();
  if(ch=='\n') return ch=0,-1;
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  x*=f;
  return ((ch!='\r')&&(ch!='\n'));
}

void add(int u,int v,int w){
  e[++tot]=(edge){v,head[u],w};
  head[u]=tot;
  e[++tot]=(edge){u,head[v],0};
  head[v]=tot;
}

bool bfs(){
  memset(lv,-1,sizeof(lv));
  queue<int> q;
  lv[st]=1;
  cur[st]=head[st];
  q.push(st);
  while(!q.empty()){
  	int u=q.front();q.pop();
  	for(int i=head[u];i!=-1;i=e[i].nxt){
  	  int v=e[i].v,val=e[i].val;
	  if(val>0&&lv[v]==-1){
	  	lv[v]=lv[u]+1;
	  	cur[v]=head[v];
	  	q.push(v);
	  }	
	}
  }
  return lv[ed]!=-1;
}

int dfs(int u,int flow){
  if(u==ed) return flow;
  int res=flow;
  for(int i=cur[u];i!=-1;i=e[i].nxt){
  	cur[u]=i;
  	int v=e[i].v,val=e[i].val;
  	if(val>0&&lv[v]==lv[u]+1){
  	  int c=dfs(v,min(res,val));
	  res-=c;
	  e[i].val-=c;
	  e[i^1].val+=c;	
	}
  }
  return flow-res;
}

int dinic(){
  int res=0;
  while(bfs()) res+=dfs(st,inf);
  return res;
}

int main(){
  /*2023.8.20 H_W_Y P2762 太空飞行计划问题 最小割最大流+最大权闭合子图*/ 
  memset(head,-1,sizeof(head));tot=-1;
  read(m);read(n);
  st=0,ed=n+m+1;
  for(int i=1,x;i<=m;i++){
  	read(a[i]);add(st,i,a[i]);sum+=a[i];
  	flag=true;
  	do{
  	  flag=read(x);
  	  add(i,x+m,inf);	
	}while(flag);
  }
  for(int i=1;i<=n;i++) read(a[i+m]),add(i+m,ed,a[i+m]);
  ans=dinic();
  for(int i=1;i<=m;i++) if(lv[i]!=-1) printf("%d ",i);
  printf("\n");
  for(int i=1;i<=n;i++) if(lv[i+m]!=-1) printf("%d ",i);
  printf("\n%d\n",sum-ans);
  return 0;
}

P4177 [CEOI2008] order

题目描述

传送门
\(m\) 个工作,\(m\) 种机器,每种机器你可以租或者买过来. 每个工作包括若干道工序, 每道工序需要某种机器来完成,你可以通过购买或租用机器来完成。租用的机器只能用一次,可以多次租用。现在给出这些参数,求最大利润。 \(1 \le n \le 1200, 1 \le m \le 1200\)

Solution

同样是最大权最小子图,只不过多了一个租用的操作
租用也是比较好处理的
我们直接把中间的边的容量 \(inf\) 改成租用的费用即可
网络流一定要剪枝!!!

H_W_Y-coding
#include <bits/stdc++.h>
using namespace std;

const int N=3e6+5,inf=0x3f3f3f3f;
int n,m,head[N],st,ed,tot=-1,sum=0,cur[N],lv[N],l,r,q[N];
struct edge{
  int v,nxt,val;
}e[N<<2];

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

void add(int u,int v,int w){
  e[++tot]=(edge){v,head[u],w};
  head[u]=tot;
  e[++tot]=(edge){u,head[v],0};
  head[v]=tot;
}

bool bfs(){
  for(int i=0;i<=ed;i++) lv[i]=-1;
  lv[st]=1;
  cur[st]=head[st];
  q[l=1]=st;r=1;
  while(l<=r){
  	int u=q[l++];
  	for(int i=head[u];i!=-1;i=e[i].nxt){
  	  int v=e[i].v,val=e[i].val;
	  if(val>0&&lv[v]==-1){
	  	lv[v]=lv[u]+1;
	  	cur[v]=head[v];
	  	q[++r]=v;
	  	if(v==ed) return true; 
	  }	
	}
  }
  return lv[ed]!=-1; 
}

int dfs(int u,int flow){
  if(u==ed) return flow;
  int res=flow;
  for(int i=cur[u];i!=-1;i=e[i].nxt){
  	cur[u]=i;
  	int v=e[i].v,val=e[i].val;
  	if(val>0&&lv[v]==lv[u]+1){
  	  int c=dfs(v,min(res,val));
  	  if(!c) lv[v]=-1;
	  res-=c;
	  e[i].val-=c;
	  e[i^1].val+=c;	
	}
	if(res==0) break;//网络流剪枝很有必要!!! 
  }
  if(res!=0) lv[u]=-1;
  return flow-res;
}

int dinic(){
  int res=0;
  while(bfs()) res+=dfs(st,inf);
  return res;
}

int main(){
  /*2023.8.23 H_W_Y P4177 [CEOI2008] order 网络流+最大权闭合子图*/ 
  memset(head,-1,sizeof(head));tot=-1;
  n=read();m=read();
  st=0,ed=n+m+1;
  for(int i=1,x,t;i<=n;i++){
  	x=read();add(st,i,x);sum+=x;
  	t=read();
  	for(int j=1,a,b;j<=t;j++){
  	  a=read();b=read();
	  add(i,a+n,b);	
	}
  }
  for(int i=1,x;i<=m;i++){x=read();add(i+n,ed,x);}
  printf("%d\n",sum-dinic());
  return 0;
}
posted @ 2023-08-19 14:30  H_W_Y  阅读(38)  评论(0)    收藏  举报