網路流經典模型之一:最大權閉合子圖(壽司餐廳)

zhangjianjunab發表於2020-10-12

前言

為毛我之前做網路流24題一篇題解都沒有寫

正片

例題

[六省聯考2017]壽司餐廳

連結

當時就想著用DP搞,結果死活搞不出那個m=1的做法

最大權閉合子圖介紹

給你新的一道題目,有 n n n個點,每個點有個 a a a值,選了就會加上其 a a a值,那麼很明顯加上全部正的 a a a即可。

但是現在要求給你一些關係:選了 x x x必須選 y y y

我們現在的 a n s ans ans先加上所有的正數。

那麼,考慮最小割,如果在最小割中割了 x x x S S S的邊,表示不選 x x x,而割了 x x x T T T的邊,然後需要注意的是,邊權不是價值,而是代價,也就是需要從 a n s ans ans中減去的東西。(當然,倒過來應該也沒有問題。)

對於一個點 i i i,如果 a i > 0 a_i>0 ai>0,則向 S S S連邊權為 a i a_i ai的邊,向 T T T連邊權為 0 0 0的邊,如果 a i ≤ 0 a_i≤0 ai0,則向 S S S連邊權為 0 0 0的邊,向 T T T連邊權為 − a i -a_i ai的邊。

好,那麼如果選了 x x x必須選 y y y,就把 x x x y y y連一條邊,邊權為 ∞ ∞ ,為什麼?

因為如果 x x x選了,割了與 T T T的邊, y y y不選,割了與 S S S的邊,那麼就一定存在一條路徑: S − > x − > y − > T S->x->y->T S>x>y>T

所以 x x x選了, y y y必須被選。

這就是最大權閉合子圖。

做法

仔細一看,對於 d i , j d_{i,j} di,j而言,選了其必須選擇 d i + 1 , j , d i , j − 1 d_{i+1,j},d_{i,j-1} di+1,j,di,j1,而對於 d i , i d_{i,i} di,i而言,選了其必須刪去 i i i的代號,而對於 i i i,如果其被選擇,那麼也要刪去其代號的平方乘 m m m

然後跑Dinic即可。

程式碼

#include<cstdio>
#include<cstring>
#define  N  110
#define  NN  12000
#define  M  110000
using  namespace  std;
inline  int  mymin(int  x,int  y){return  x<y?x:y;}
struct  node
{
	int  y,next,c;
}a[M];int  len=1,last[NN];
inline  void  ins_node(int  x,int  y,int  c){len++;a[len].y=y;a[len].c=c;a[len].next=last[x];last[x]=len;}
inline  void  ins(int  x,int  y,int  c){ins_node(x,y,c);ins_node(y,x,0);}
int  list[NN],head,tail,h[NN],st,ed;
bool  bfs()
{
	memset(h,0,sizeof(h));h[ed]=1;
	head=1;tail=1;list[1]=ed;
	while(head<=tail)
	{
		int  x=list[head++];
		for(int  k=last[x];k;k=a[k].next)
		{
			int  y=a[k].y;
			if(a[k^1].c  &&  !h[y])
			{
				h[y]=h[x]+1;
				list[++tail]=y;
			}
		}
	}
	return  h[st];
}
int  dinic(int  x,int  f)
{
	if(x==ed)return  f;
	int  s=0,t;
	for(int  k=last[x];k;k=a[k].next)
	{
		int  y=a[k].y;
		if(a[k].c  &&  h[x]==h[y]+1)
		{
			s+=t=dinic(y,mymin(f-s,a[k].c));
			a[k].c-=t;a[k^1].c+=t;
			if(s==f)return  s;
		}
	}
	h[x]=0;
	return  s;
}
bool  v[1100];
int  ans=0;
inline  void  jian(int  x,int  v)
{
	if(v>0)
	{
		ans+=v;
		ins(st,x,v);
	}
	else  if(v<0)ins(x,ed,-v);
}
int  n,m,d[N][N];
int  main()
{
	scanf("%d%d",&n,&m);
	st=n*n+n+1000+1;ed=st+1;
	for(int  i=1;i<=n;i++)
	{
		int  x;scanf("%d",&x);
		if(!v[x]  &&  m)jian(n*n+x,-m*x*x),v[x]=1;
		jian(n*n+1000+i,-x);
		ins(n*n+1000+i,n*n+x,999999999);
	}
	for(int  i=1;i<=n;i++)
	{
		for(int  j=i;j<=n;j++)
		{
			scanf("%d",&d[i][j]);
			jian((i-1)*n+j,d[i][j]);
		}
	}
	for(int  i=1;i<=n;i++)
	{
		for(int  j=i;j<=n;j++)
		{
			if(i==j)ins((i-1)*n+j,n*n+1000+i,999999999);
			else  ins((i-1)*n+j,i*n+j,999999999),ins((i-1)*n+j,(i-1)*n+j-1,999999999);
		}
	}
	while(bfs())
	{
		ans-=dinic(st,999999999);
	}
	printf("%d\n",ans);
	return  0;
}
/*
1-n^2表示d(i,j)
n^2+1-n^2+1000表示第i個代號。
n^2+1001-n^2+1000+n表示第i個數字,減去其代號 
*/

相關文章