• 【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减去最小割即为答案
    }
    
  • 相关阅读:
    JAVA多线程实现的三种方式
    Java String charAt()方法
    三大数据库分页方法
    SSH框架 spring 配置中的: scope="prototype"
    SSH中的Invalid action class configuration that references an unknown class named.......
    String类中toCharArray()方法的用法
    SQL Server中DateTime与DateTime2的区别
    Java 集合系列11之 Hashtable详细介绍(源码解析)和使用示例
    Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例
    哈希表及处理冲突的方法
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4873.html
Copyright © 2020-2023  润新知