P3749 [六省联考2017]寿司餐厅(最大权闭合子图变形)

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

最大权闭合子图

有两种建图方式

下面 a [ i ] a_[i] 表示寿司 i i 的编号

第二种建图其实就是把 d i , i 寿 d_{i,i}也当成一个寿司而已

. d i j , 寿 , \color{Red}{Ⅰ.对每个d_{ij},每个寿司,每种编号都新建一个点}

d i j , s d i j , i n f 若d_{ij}为正,源点s连向d_{ij},流量inf

d i j , d i j t , i n f 若d_{ij}为负,d_{ij}连向汇点t,流量inf

d i j [ i , j ] 寿 , i n f 每个d_{ij}向区间[i,j]的寿司连边,流量inf

d i j d i + 1   j d i   j 1 , i n f 每个d_{ij}向d_{i+1 \ j}和d_{i\ j-1}连边,流量inf

寿 t , a [ i ] 每个寿司向汇点t连边,流量为a[i]

寿 , i n f 每个寿司向对应的编号连边,流量inf

寿 t , m a [ i ] a [ i ] 每个寿司编号向汇点t连边,流量为m*a[i]*a[i]

跑最大流最小割即可

考虑一下正确性

d i j s 因为权值为正的d_{ij}连在s

s d i j , d i j , , t 如果割掉s到d_{ij}的边,表示不选择d_{ij},减去收益,而且必定到不了t

s d i j , , s t 如果不割掉s到d_{ij}的边,不需要减去收益,但为了保证s到t不连通

d i j 寿 t a [ i ] , d_{ij}指向的寿司到t有流量a[i]的边,所以一定会被割掉

d i j 寿 i n f , 寿 d_{ij}指向的寿司由于流量是inf,所以可以流过去到对应的寿司编号

[ i , j ] 寿 t m a [ i ] a [ i ] 那么一定会把[i,j]的寿司对应的编号连到t的m*a[i]*a[i]割掉

, m a [ i ] 2 + c a [ i ] , 综上所诉,构造了m*a[i]^2+c*a[i],所以正确

, d i j d i + 1   j d i   j 1 , i n f 又因为选择大区间必须选择小区间,所以每个d_{ij}向d_{i+1 \ j}和d_{i\ j-1}连边,流量inf

#include <bits/stdc++.h>
using namespace std;
const int maxn=2e6+10;
const int inf=1e9;
int n,m,s,t,sumn;
int a[maxn],id[509][509],w[509][509],num,ok[maxn],dis[maxn],idsi[maxn];
struct edge{
	int to,nxt,flow;
}d[maxn]; int head[maxn],cnt=1;
void add(int u,int v,int flow){
	d[++cnt]=(edge){v,head[u],flow},head[u]=cnt;
	d[++cnt]=(edge){u,head[v],0},head[v]=cnt;
}
bool bfs()
{
	for(int i=0;i<=t;i++)	dis[i]=0;
	dis[s]=1;
	queue<int>q; q.push( s );
	while( !q.empty() )
	{
		int u=q.front(); q.pop();
		for(int i=head[u];i;i=d[i].nxt )
		{
			int v=d[i].to;
			if( d[i].flow&&dis[v]==0 )
			{
				dis[v]=dis[u]+1;
				if( v==t )	return true;
				q.push( v );
			}
		}
	}
	return false;
}
int dinic(int u,int flow)
{
	if( u==t )	return flow;
	int res=flow;
	for(int i=head[u];i&&res;i=d[i].nxt )
	{
		int v=d[i].to;
		if( dis[v]==dis[u]+1&&d[i].flow)
		{
			int temp=dinic(v,min(res,d[i].flow) );
			if( temp==0 )	dis[v]=0;
			res-=temp;
			d[i].flow-=temp;
			d[i^1].flow+=temp;
		}
	}
	return flow-res;
}
int main()
{
	cin >> n >> m;
	for(int i=1;i<=n;i++)	cin >> a[i];
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	{
		cin >> w[i][j];
		id[i][j]=++num;
	}
	for(int i=1;i<=n;i++)//种类 
		if( !ok[a[i]] )	ok[a[i]]=1,idsi[a[i]]=++num;
	s=0,t=num+n+1;
	memset(ok,0,sizeof(ok));
	for(int i=1;i<=n;i++)//种类向汇点连mx^2 
		if( !ok[a[i]] )	add(idsi[a[i]],t,m*a[i]*a[i] ),ok[a[i]]=1;
	for(int i=1;i<=n;i++)
	{
		add(num+i,idsi[a[i]],inf);//连向种类 
		add(num+i,t,a[i] );//连向汇点 
	}
	for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	{
		add(id[i][j],num+i,inf);
		add(id[i][j],num+j,inf);
		/*for(int q=i;q<=j;q++)
			add(id[i][j],num+q,inf);*/
		if( w[i][j]>0 )
		{
			sumn+=w[i][j];
			add(s,id[i][j],w[i][j] );
		}
		else
			add(id[i][j],t,-w[i][j] );
		if( i!=j )	
		{
			add(id[i][j],id[i+1][j],inf);
			add(id[i][j],id[i][j-1],inf);
		}
	}
	int ans=0;
	while( bfs() )	ans+=dinic(s,inf);
	cout << sumn-ans;
}

. d i j , 寿 \color{Red}{Ⅱ.只对d_{ij},寿司编号新建一个点}

t , m a [ i ] a [ i ] , 每种编号向汇点t连边,流量为m*a[i]*a[i],表示选了这种类型就要付钱

d i j d i + 1   j d i   j 1 i n f , d_{ij}向d_{i+1\ j}和d_{i\ j-1}连流量inf的边,表示选了大区间必须选小区间

d i , i , i n f d_{i,i}向对应编号连边,流量inf

t a [ i ] a [ i ] , 编号向汇点t连流量a[i]*a[i]的边,表示选了这种类型就要付钱

d i , j , , d i , j d_{i,j}为正向源点连边,否则连汇点,流量d_{i,j}

, i = = j a [ i ] , d i , j a [ i ] 特殊的,当i==j需要额外付a[i]的费用,所以流量是d_{i,j}-a[i]

建图代码(抄的小粉兔代码)

int main() {
	scanf("%d%d", &N, &M);
	for (int i = 1; i <= N; ++i) scanf("%d", &A[i]), MxA = std::max(MxA, A[i]);
	DinicFlow::S = 1, DinicFlow::T = 2;
	cnt = 2;
	for (int i = 1; i <= N; ++i) for (int j = i; j <= N; ++j) {
		scanf("%d", &F[i][j]), Id[i][j] = ++cnt;
	}
	for (int i = 1; i <= N; ++i) for (int j = i; j <= N; ++j) {
		int cost = F[i][j];
		if (i == j) {
			if (M) DinicFlow::insw(Id[i][j], cnt + A[i], Inf);
			cost -= A[i];
		}
		else {
			DinicFlow::insw(Id[i][j], Id[i + 1][j], Inf);
			DinicFlow::insw(Id[i][j], Id[i][j - 1], Inf);
		}
		if (cost > 0) DinicFlow::insw(1, Id[i][j], cost), Ans += cost;
		if (cost < 0) DinicFlow::insw(Id[i][j], 2, -cost);
	}
	if (M) for (int i = 1; i <= MxA; ++i) DinicFlow::insw(++cnt, 2, i * i);
	DinicFlow::N = cnt;
	printf("%lld\n", Ans - DinicFlow::Dinic());
	return 0;
}

猜你喜欢

转载自blog.csdn.net/jziwjxjd/article/details/108451493