[HEOI2017] 寿司餐厅 + 最大权闭合子图的总结

发布日期:2019-04-18

Description

太长了自己看叭 点这里!

Solution

先学一波什么叫最大权闭合子图。

先要明白什么是闭合子图,闭合子图就是给定一个有向图,从中选择一些点组成一个点集V。对于V中任意一个点,其后续节点都仍然在V中。如果给点再加上点权那就会有很多问题可以转化成求最大权闭合子图了。

那怎么求这个最大权闭合子图呢?

可以用网络流的思想来求出来这个东西。

先说建图,如果一个点的权值是正的,那就让源点和这个点相连,流量是这个点的权值。如果一个点的权值是负的,那就让这个点和汇点相连,流量是这个点权值的绝对值。那些在原图中的边我们还是让两个端点相连,流量为正无穷。然后这张图的最小割就是我们要求的最大权闭合子图了。

证明:

先来证明两个引理:

1.最小割一定是简单割

所谓简单割就是割(ST)中每条边都与S或T直接相连。

因为不与st相连的边就是原图中的那些边,它们的流量都为正无穷,所以包含流量为正无穷的边的割一定不是最小割。

2.一个简单割一定和一个闭合子图对应

我们将原图中的闭合子图V加上源点s构成S集,其余点和汇点构成T集。

首先证明闭合子图是简单割:若闭合子图对应的割不是简单割,那么就存在一条原图中的边(uv)u∈S,v∈T,且flow(uv)=inf。说明u的后续节点v不在S中,这与闭合子图的定义矛盾。

接着证明简单割是闭合子图,对于V中任意一个点uu∈S。u的任意一条出边flow(uv)=inf,一定不会在简单割的边集中,因此v不属于Tv∈S。符合闭合子图的定义。

由上面两个引理可以知道,最小割也对应了一个闭合子图,接下来证明最小割就是最大权的闭合子图。

首先有割的容量C(ST)=T中所有正权点的权值之和+S中所有负权点的权值绝对值之和。

闭合子图的权值W=S中所有正权点的权值之和-S中所有负权点的权值绝对值之和。

则有C(ST)+W=T中所有正权点的权值之和+S中所有正权点的权值之和=所有正权点的权值之和。

所以W=所有正权点的权值之和-C(ST)

由于所有正权点的权值之和是一个定值,那么割的容量越小,W也就越大。因此当C(ST)取最小割时,W也就达到了最大权。

证毕。

那有一些最大权闭合子图的裸题就可以做了,比如说 拍照 太空飞行计划问题

然后我们再回过头来看这道题。

哦对了,最大权闭合子图的模型大概长这样:

    选一些点必须要选其他点每个点最多选一次有点权求最大点权和

熟练的话大概可以猜到这是一道最大权闭合子图的题?

于是直接建模九星了

    超级源点S向正美味度的区间连一条流量为美味度的边负美味度区间向超级汇点连一条流量为美味度绝对值的边区间[ij]向区间[i+1j][ij-1]分别连一条流量为inf的边区间[ii]向寿司i连一条流量为inf的边寿司i向超级汇点连一条流量为寿司代号的边寿司i向其代号连一条流量为inf的边寿司代号i向超级汇点连一条流量为mii的边

然后是吐槽...

做这题时非要递归连边然后没注意一个区间可能被递归到了好几次(或许是好几万次)就直接导致一个n=30的数据点我连了三十亿条边mdzz

哦对了顺便%yzy学长他考场上秒切了这题orzzzzz

Code

#include<bits/stdc++.h>using std::minusing std::maxusing std::swapusing std::vectortypedef double dbtypedef long long ll#define pb(A) push_back(A)#define pii std::pair<intint>#define all(A) A.begin()A.end()#define mp(AB) std::make_pair(AB)const int N=105const int M=1e5+5const int inf=1e9int head[M]d[M]int nmstll cnt=1a[N]int dis[N][N]totvis[N*10]struct Edge{ int tonxtflow}edge[1000005]void add(int xint yint z){ edge[++cnt].to=y edge[cnt].nxt=head[x] edge[cnt].flow=z head[x]=cnt}int getint(){ int X=0w=0char ch=getchar() while(!isdigit(ch))w|=ch=="-"ch=getchar() while( isdigit(ch))X=X*10+ch-48ch=getchar() if(w) return -Xreturn X}bool bfs(){ std::queue<int> qq.push(s) memset(d0sizeof d)d[s]=1 while(q.size()){ int u=q.front()q.pop() for(int i=head[u]ii=edge[i].nxt){ int to=edge[i].to if(!d[to] and edge[i].flow){ d[to]=d[u]+1 q.push(to) if(to==t) return 1 } } } return 0}int dinic(int nowint flow){ if(now==t) return flow int res=flow for(int i=head[now]ii=edge[i].nxt){ int to=edge[i].to if(d[to]==d[now]+1 and edge[i].flow){ int k=dinic(tomin(edge[i].flowres)) if(!k) d[to]=0 edge[i].flow-=kedge[i^1].flow+=kres-=k if(!res) return flow } } return flow-res}int calc(int xint y){ int now=(x-1)*(n+n-(x-1)+1)/2 return now+y-x+1}signed main(){ n=getint()m=getint()ll ans=0 tot=n*(n+1)/2t=tot+n+1001 for(int i=1i<=ni++) a[i]=getint() for(int i=1i<=ni++) for(int j=ij<=nj++){ dis[i][j]=getint() if(dis[i][j]>0) add(scalc(ij)dis[i][j])add(calc(ij)s0)ans+=dis[i][j] if(dis[i][j]<0) add(calc(ij)t-dis[i][j])add(tcalc(ij)0) } for(int i=1i<=ni++){ int x=calc(ii) add(xtot+iinf)add(tot+ix0) for(int j=i+1j<=nj++){ int a=calc(ij)b=calc(ij-1)c=calc(i+1j) add(abinf)add(ba0) add(acinf)add(ca0) } } for(int i=1i<=ni++){ add(tot+itot+n+a[i]inf)add(tot+n+a[i]tot+i0) add(tot+ita[i])add(ttot+i0) if(!vis[a[i]]){ add(tot+n+a[i]tm*a[i]*a[i])add(ttot+n+a[i]0) vis[a[i]]=1 } } ll flow=0mxflow=0 while(bfs()) while(flow=dinic(sinf)) mxflow+=flow printf("%lld"ans-mxflow) return 0}