【BZOJ4873】[SHOI2017] 寿司餐厅(最大权闭合子图模型)

点此看题面

大致题意:(n)种寿司,各有一个代号(a_i),你可以无限次选取任一区间内的所有寿司。如果你选的区间包含区间([i,j]),就可以获得(d_{i,j})的美味度(一个区间即便被选择多次,也只会计算一次美味度)。如果你一共选过(c)种代号为(x)的寿司,就需要支付(mx^2+cx)的代价。求美味度减代价的最大值。

最大权闭合子图

说实话这个模型我肯定知道的,但已经记不起来什么时候写过了(反正我试着去翻了翻博客没找到)。不过在点开题解之前我早已完全忘光这东西的概念及相关应用了。

考虑一系列关系(x ightarrow y)表示选择了(x)就必须选择(y),且每个物品有一个权值(a_i)

则我们建立一个超级源和一个超级汇,用一种割边方式对应一种选择方案:割超级源的边表示不选该点,割超级汇的边表示选该点。

然后先根据权值的正负性连上每个节点的边:

  • (a_x>0):从超级源向该点连一条流量为(a_x)的边,表示不选这个点就要付出(a_x)的代价。
  • (a_x<0):从该点向超级汇连一条流量为(-a_x)的边,表示选了这个点就要付出(-a_x)的代价。

接着就要想办法把(x ightarrow y)的依赖关系表示成边,于是我们从(x)(y)连一条流量为(INF)的边(保证表示关系的边不会被割掉),发现:

  • 如果选择了(x)(即割了(x)到超级汇的边),却不选择(y)(即保留(y)到超级汇的边),那么就存在一条从超级源到(x),经过(y),最后到超级汇的路径,显然是不合法的。也就是说,选择了(x)就必须选择(y)
  • 如果不选择(x)(即割了超级源到(x)的边),那么不会有路径通向(x),也就不会有路径经由这条关系边到达(y),所以不会对(y)如何选择造成任何影响。

此时如果要求能得到的最大权值,我们可以用一个(ans)统计所有满足(a_x>0)(a_x)之和,然后减去这张图的最小割(最小割=最大流,可以用(Dinic)求解),就能得到答案了。

关于此题的转化

对于这道题,显然有:

  • 如果我们选择了(d_{i,j}),就必须选择(d_{i+1,j})(d_{i,j-1})。(这两个区间还会继续递归,最终就变成必须选择区间([i,j])内的所有区间)
  • 如果我们选择了(d_{i,i}),首先需要支付(a_i)的代价,然后我们还必须选择(a_i)这种代号(即需要支付(ma_i^2)的代价)。实际上我们可以把每种代号也看做一个物品,同样根据依赖关系连边。

得出了依赖关系,就可以按照上面的方式建图跑网络流了。

具体实现详见代码。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define A 1000
#define LL long long
#define INF 1e9
using namespace std;
int n,m,a[N+5],p[N+5][N+5];
template<int NS,int ES> class NewFlow//网络流
{
	private:
		#define s 1
		#define t 2
		#define add(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f)
		#define E(x) ((((x)-1)^1)+1)
		int ee,lnk[NS+5],cur[NS+5],q[NS+5],d[NS+5];struct edge {int to,nxt,F;}e[2*ES+5];
		I bool BFS(CI n)
		{
			RI i,k,H=1,T=1;for(i=1;i<=n;++i) d[i]=0;d[q[1]=s]=1;W(H<=T&&!d[t])
				for(i=lnk[k=q[H++]];i;i=e[i].nxt) !d[e[i].to]&&e[i].F&&(d[q[++T]=e[i].to]=d[k]+1);
			return d[t]?memcpy(cur,lnk,sizeof(lnk)),1:0;
		}
		I int DFS(CI x,RI f)
		{
			if(x==t||!f) return f;RI w,res=0;
			for(int& i=cur[x];i;i=e[i].nxt)
			{
				if((d[x]+1)^d[e[i].to]||!(w=DFS(e[i].to,min(f,e[i].F)))) continue;
				if(e[i].F-=w,e[E(i)].F+=w,res+=w,!(f-=w)) break;
			}return !res&&(d[x]=-1),res;
		}
	public:
		I void Add(CI x,CI y,CI f) {add(x,y,f),add(y,x,0);}
		I LL MaxFlow(CI n) {RI i;LL f=0;W(BFS(n)) f+=DFS(s,INF);return f;}
};NewFlow<N*N+A,3*N*N+A> F;
int main()
{
	RI i,j;LL ans=0;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",a+i);
	RI k=2;for(i=1;i<=n;++i) for(j=i;j<=n;++j) p[i][j]=++k;
	RI x;for(i=1;i<=n;++i) for(j=i;j<=n;++j) scanf("%d",&x),
		i^j?(F.Add(p[i][j],p[i+1][j],INF),F.Add(p[i][j],p[i][j-1],INF),0)//对于大区间,向两个小区间连边
		:(x-=a[i],m&&(F.Add(p[i][j],k+a[i],INF),0)),//对于单个点,首先要支付a[i]的代价,然后必须选择a[i]这种代号
		x>0?ans+=x,F.Add(s,p[i][j],x):F.Add(p[i][j],t,-x);//向超级源/汇连边
	if(m) for(i=1;i<=A;++i) F.Add(k+i,t,i*i);//每种代号向超级汇连边
	return printf("%lld
",ans-F.MaxFlow(k+A)),0;//用统计得到的ans减去最小割即为答案
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4873.html