您的位置:首页 > 其它

从bzoj2039,3144 小议最小割模型建立法

2017-12-07 13:31 281 查看

bzoj2039

题目分析

我们要以得失平衡的角度来看待最小割模型建立问题。   ——沃兹基朔德

人类的第六感告诉我们,这题涉及多人之间的得和失,有可能是一种最小割模型。

根据伟大的文学家沃兹基的话,我们可以知道,最小割模型的一般建立方法是源点流出“得”到每一个点,而每一个点的出边对应每一个选择的“失”,网络流“流走”的过程就相当于你本来得到的东西失去了的过程,所以求最小割,得到的就是你最少要失去多少东西,然后用可得-最少失去=最大获得。

事实上,著名的最大点权独立集问题和最大权闭合子图问题的思想都类似于此。

所以我们把伟大的文学家沃兹基的思想应用到这道题上,建模方法如下:

每个人是一个点。对于点i:

连边(s,i,∑nj=1Ei,j),表示可得。

再连边(i,t,ai)表示选择这个人会失去的。

连边(i,j,Ei,j∗2),表示不选这个人会失去的。会失去的值应该是减去选了这个人后得到的贡献,再减去不选这个人,这个人会干扰j的工作,造成的影响。

最后答案应该是∑ni=1∑nj=1Ei,j-最小割

代码

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N=1005,M=2004005;const LL inf=1e14;
int n,s,t,tot=1;LL ans;
int h
,ne[M],to[M],lev
,q
;LL flow[M];
void add(int x,int y,LL z) {
to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
LL dfs(int x,LL liu) {
if(x==t) return liu;
LL kl,sum=0;
for(int i=h[x];i;i=ne[i])
if(lev[to[i]]==lev[x]+1&&flow[i]>0) {
kl=dfs(to[i],min(liu-sum,flow[i]));
sum+=kl,flow[i]-=kl,flow[i^1]+=kl;
if(sum==liu) return sum;
}
if(!sum) lev[x]=-1;
return sum;
}
int bfs() {
for(int i=s;i<=t;++i) lev[i]=0;
int x,he=1,ta=1;lev[s]=1,q[1]=s;
while(he<=ta) {
x=q[he],++he;
if(x==t) return 1;
for(int i=h[x];i;i=ne[i])
if(!lev[to[i]]&&flow[i]>0)
q[++ta]=to[i],lev[to[i]]=lev[x]+1;
}
return 0;
}
int main()
{
LL x;
scanf("%d",&n);s=0,t=n+1;
for(int i=1;i<=n;++i) scanf("%lld",&x),add(i,t,x);
for(int i=1;i<=n;++i) {
LL kl=0;
for(int j=1;j<=n;++j) {
scanf("%lld",&x),kl+=x;
if(i!=j) add(i,j,x+x);
}
add(s,i,kl),ans+=kl;
}
while(bfs()) ans-=dfs(s,inf);
printf("%lld",ans);
return 0;
}


bzoj3144

题目大意

我们姑且把切糕看成一个由p∗q个高度为r的柱子组成的图形,在每一个竖着的“柱子”上要选择一个点,相邻“柱子”上选择的点高度差不大于d

题目分析

选择割边,是选择的一种体现。   ——沃兹基朔德

根据伟大文学家沃兹基的话,我们可以用割边作为选择。

首先对于切糕上的每一个点,图中建立一个点,然后建立一个“虚拟”层,相当于把“柱子”增高了。(其实也可以不用建,但这样子比较不容易出错)

首先s向第一层的点都连一条流量为inf的边。

然后最后一层(虚拟层)向汇点连一条流量为inf的边。

然后对于点i,我们把i向与i在同一个“柱子”但是在i下方的点连一条流量为i的不和谐值的边。把这条边割掉,代表选择了这个点。这样我们发现,每一根“柱子”上的点连成了一条链,为了求出最小割,每一条“链条”上都必须有一条边被割掉,相当于在“柱子”上选点。

然后就是限制条件,相邻“柱子”之间的点不能相差d的那个。

也就是说,如果相邻“链条”上选择割掉的那条边代表的点(这条边的起点)与其距离差大于了d,那么此时这根“链条”上依然存在一条可以到达汇点的路径。

也就是连边(id(x1,y1,z1),id(x2,y2,z1−d),inf),这样保证(x2,y2,z1−d)以上的点在(x1,y1,z1)被选择后不会被选择,由于(x2,y2)这根柱子上的点也会这样连边一次,所以在(x1,y1,z1)下面的不能选的点也被考虑了。

答案就是最小割。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=45*45*45+5,M=3e5+5,inf=0x3f3f3f3f;
int P,Q,R,d,s,t,tot=1,ans;
int h
,ne[M],to[M],flow[M],q
,lev
,bj[45][45][45];
int mvx[6]={0,1,-1,0,0},mvy[6]={0,0,0,1,-1};
void add(int x,int y,int z) {
to[++tot]=y,ne[tot]=h[x],h[x]=tot,flow[tot]=z;
to[++tot]=x,ne[tot]=h[y],h[y]=tot,flow[tot]=0;
}
int dfs(int x,int liu) {
if(x==t) return liu;
int kl,sum=0;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&lev[to[i]]==lev[x]+1) {
kl=dfs(to[i],min(liu-sum,flow[i]));
flow[i]-=kl,flow[i^1]+=kl,sum+=kl;
if(sum==liu) return sum;
}
if(!sum) lev[x]=-1;
return sum;
}
int bfs() {
for(int i=s;i<=t;++i) lev[i]=0;
int ta=1,he=1,x; q[1]=s,lev[s]=1;
while(he<=ta) {
x=q[he],++he;
if(x==t) return 1;
for(int i=h[x];i;i=ne[i])
if(flow[i]>0&&!lev[to[i]])
lev[to[i]]=lev[x]+1,q[++ta]=to[i];
}
return 0;
}
void init() {
scanf("%d%d%d%d",&P,&Q,&R,&d);
s=0,t=P*Q*(R+1)+1;int cnt=0;
for(int i=1;i<=R+1;++i)
for(int j=1;j<=P;++j)
for(int k=1;k<=Q;++k) bj[i][j][k]=++cnt;
}
int main()
{
int x;init();
for(int i=1;i<=R;++i)
for(int j=1;j<=P;++j)
for(int k=1;k<=Q;++k)
scanf("%d",&x),add(bj[i][j][k],bj[i+1][j][k],x);
for(int i=1;i<=P;++i)
for(int j=1;j<=Q;++j)
add(s,bj[1][i][j],inf),add(bj[R+1][i][j],t,inf);
for(int x1=1;x1<=P;++x1)
for(int y1=1;y1<=Q;++y1) {
for(int k=1;k<=4;++k) {
int x2=x1+mvx[k],y2=y1+mvy[k];
if(x2<1||x2>P||y2<1||y2>Q) continue;
for(int t=d+1;t<=R;++t) add(bj[t][x1][y1],bj[t-d][x2][y2],inf);
}
}
while(bfs()) ans+=dfs(s,inf);
printf("%d",ans);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: