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

点此看题面

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

最大权闭合子图

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

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

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

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

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

接着就要想办法把\(x\rightarrow 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\n",ans-F.MaxFlow(k+A)),0;//用统计得到的ans减去最小割即为答案
}

猜你喜欢

转载自www.cnblogs.com/chenxiaoran666/p/BZOJ4873.html