【BZOJ 1001】【Beijing 2006】狼抓兔子

看到这题,是个OIer应该都能想到最小割,然后条件反射写最大流,然后敲ISAP,Dinic,.......然后TLE掉了= =

 = =

本蒟蒻就是这样,然后去ym了各种题解,最后发现这是一个特殊的最大流模型,可以转化为最短路。现在讲一下详细建图方法。详见《两极相通—浅析最大最小定理在信息学竞赛中的应用》by 周冬

显然这个图是一个平面图,并且s,t在两个没有边界的平面上,这样的图称为s-t平面图,s-t上的最大流=最小割=对偶图上的最短路。

那么什么是对偶图呢,就是把每个平面当作点,平面与平面的公共边变成点与点之间的双向边,如何区分对偶图的s',t'呢,把原图的s,t连起来,构成一个新的平面,新平面视作s‘,无限面视作t',注意对偶图中s',t'之间的边要删去。形象一点,上个图(源自周冬论文):

很显然最小割变成了新图的最短路(证明见论文),原图的每一可行割,对应新图的每一可行路径,问题至此转化为对偶图上最短路。code:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
struct hp{
	int v,c;
}a[6000200];
int queue[4000200];
bool exist[2000200];
int point[2000200],next[6000200],dis[2000020];
int n,m,e=1;
void add(int u,int v,int c)
{
	e++; a[e].v=v; a[e].c=c; next[e]=point[u]; point[u]=e;
	e++; a[e].v=u; a[e].c=c; next[e]=point[v]; point[v]=e;
}
int spfa()
{
	int head,tail,u,i;
	memset(exist,false,sizeof(exist));
	memset(dis,127/3,sizeof(dis));
	head=0; tail=1; queue[tail]=0; dis[0]=0; exist[0]=true;
	while (head!=tail)
	  {
	    head=head%4000200+1;
		u=queue[head]; exist[u]=false;
		for (i=point[u];i;i=next[i])
		  {
		    if (dis[a[i].v]>dis[u]+a[i].c)
		      {
		        dis[a[i].v]=dis[u]+a[i].c;
		        if (!exist[a[i].v])
		          {
		            tail=tail%4000200+1; 
				    exist[a[i].v]=true;
		            queue[tail]=a[i].v;
		          }
		      }
	      }
	  }
	return dis[2*(n-1)*(m-1)+1];
}
int main()
{
	int i,j,x,ans=2100000000;
	scanf("%d%d",&n,&m);
	for (i=1;i<=n;++i)
      for (j=1;j<=m-1;++j)
        {
          scanf("%d",&x);
          if (n==1)
		    ans=min(ans,x);         
		  if (i==1)
		    add(j,2*(n-1)*(m-1)+1,x);
		  if (i>1&&i<n)
		    add((m-1)*2*(i-1)+j,(m-1)*(2*(i-1)-1)+j,x);
		  if (i==n)
		    add(0,(m-1)*(2*(n-1)-1)+j,x);
        }
    for (i=1;i<=n-1;++i)
      for (j=1;j<=m;++j)
        {
          scanf("%d",&x);
          if (m==1)
            ans=min(ans,x);
          if (j==1)
            add(0,(m-1)*(2*i-1)+1,x);
          if (j>1&&j<m)
            add((m-1)*(2*(i-1))+j-1,(m-1)*(2*i-1)+j,x);
          if (j==m)
            add((m-1)*(2*i-1),2*(n-1)*(m-1)+1,x);
        }
    for (i=1;i<=n-1;++i)
      for (j=1;j<=m-1;++j)
        {
          scanf("%d",&x);
		  add((m-1)*(2*i-1)+j,2*(m-1)*(i-1)+j,x);    
        }
    if (n==1&&m==1)
      printf("0\n");
    else
      {
        if (n==1||m==1) printf("%d\n",ans);
        else printf("%d\n",spfa());
      }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值