既然选择了远方,便只顾风雨兼行|

H_W_Y

园龄:1年11个月粉丝:28关注:15

2023-08-19 14:30阅读: 14评论: 0推荐: 0

20230812-网络流 I

20230812

最小路径覆盖

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

P2764 最小路径覆盖问题

题目描述

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

Solution

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

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 个单位派出了 ri 个代表。 会议的餐厅共有 n 张餐桌,第 i 张餐桌可容纳 ci 个代表就餐。
为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。
请给出一个满足要求的代表就餐方案。
1m1501n2701ri,ci103

Solution

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

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×m 的网格中,有一些格子有石柱,石柱的高度为 13
有一些石柱的顶上有蜥蜴,蜥蜴每次可以跳到距离不超过 d 的石柱上,或者跳到界外。
蜥蜴跳一次, 它所在的石柱的高度就减一,如果某个石柱的高度为 0 了,石柱就消失,以后蜥蜴不能跳到这里。现在要使得剩下无法逃脱的蜥蜴数最少。
1r,c20,1d4

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, 的 球。

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

Solution

发现如果对于前面的 m 个数
那么我们可以把这 m 个数按照可以相邻的关系建立一个图
在这个图上跑出最小路径覆盖
如果最小路径覆盖 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×m1n,m200的格子纸,格子要么涂黑(#)要么涂白(.)。你需要用若干个长为一,宽为任意正整数或者宽为一,长为任意正整数的长方形去覆盖所有黑色格子,要求不能盖到白色格子上,不能盖到其他长方形上,不能盖出格子纸的边界,求最少用多少个长方形。

Solution

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

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

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>0 的边走下去
能走到的点就是最小割

最大权闭合子图

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

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

P2762 太空飞行计划问题

题目描述

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

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 种机器,每种机器你可以租或者买过来. 每个工作包括若干道工序, 每道工序需要某种机器来完成,你可以通过购买或租用机器来完成。租用的机器只能用一次,可以多次租用。现在给出这些参数,求最大利润。 1n1200,1m1200

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

本文作者:H_W_Y

本文链接:https://www.cnblogs.com/H-W-Y/p/17642433.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   H_W_Y  阅读(14)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起