[NOI2010]海拔(最小割)

【题解】

先寻找每个点的最优高度范围:
首先每个点的海拔不可能比周围四个点都高或都低,然后设某点高度为x,假设围四个点高度已知,随便考虑几种情况,可以发现:总体力值关于x单调(增或减)
因此可以得出结论:每个点的海拔都与周围的某个点相同 

=> 可以推出(我是猜出)每个点高度都是0或1,且按高度标记每个点后,图上只有2个连通块 


然后就是熟悉的最小割转最短路了,给每个域编号后建新图,要注意建的有向边的方向(见图)


比如:规定两个上下相邻、"期望海拔方案"为上0下1的点的连边,对应新图中的边的方向为:从右到左 
          两个左右相邻、"期望海拔方案"为左0右1的点的连边,对应新图中的边的方向为:从上到下 


【代码】

#include<stdio.h>
#include<stdlib.h>
typedef long long LL;
LL w[2000000]={0},d[3000000]={0},heap[300000]={0};//heap[i]:堆的第i个位置对应元素的序号 
int v[2000000]={0},first[300000]={0},next[2000000]={0},pos[300000]={0};//pos[i]:第i个元素在堆中的的序号,heap[]与pos[]作用相反 
const LL INF=1000000000000000000ll;
int e=0,node;
void jh_heap(int a,int b)
{
	int t=pos[heap[a]];//"先交换pos再heap"不必须 
	pos[heap[a]]=pos[heap[b]];
	pos[heap[b]]=t;
	t=heap[a];
	heap[a]=heap[b];
	heap[b]=t;
}
void tj(int x,int y,int z)
{
	v[++e]=y;
	w[e]=(LL)z;
	next[e]=first[x];
	first[x]=e;
}
void tz(int x)
{
	int i;
	for(i=x;i>1;i/=2)
	{
		if(d[heap[i/2]]>d[heap[i]]) jh_heap(i/2,i);
		else return;
	}
}
void sc()
{
	int i=1;
	jh_heap(1,node);
	node--;
	while(i*2<=node)
	{
		i*=2;
		if(i+1<=node&&d[heap[i]]>d[heap[i+1]]) i++;
		if(d[heap[i/2]]>d[heap[i]]) jh_heap(i/2,i);
		else return;
	}
}
int main()
{
	int n,i,j,s,t,x;
	scanf("%d",&n);
	s=n*n+1;
	t=s+1;
	for(i=1;i<=n+1;i++)//西->东(左0右1),从上到下建边 
		for(j=1;j<=n;j++)
		{
			scanf("%d",&x);
			if(i==1) tj(s,j,x);
			if(i>1&&i<=n) tj((i-2)*n+j,(i-1)*n+j,x);
			if(i>n) tj((i-2)*n+j,t,x);
		}
	for(i=1;i<=n;i++)//北->南(上0下1),从右到左建边 
		for(j=1;j<=n+1;j++)
		{
			scanf("%d",&x);
			if(j==1) tj((i-1)*n+j,t,x);
			if(j>1&&j<=n) tj((i-1)*n+j,(i-1)*n+j-1,x);
			if(j>n) tj(s,i*n,x);
		}
	for(i=1;i<=n+1;i++)//东->西(左1右0),从下到上建边 
		for(j=1;j<=n;j++)
		{
			scanf("%d",&x);
			if(i>1&&i<=n) tj((i-1)*n+j,(i-2)*n+j,x);
		}
	for(i=1;i<=n;i++)//南->北(上1下0),从左到右建边 
		for(j=1;j<=n+1;j++)
		{
			scanf("%d",&x);
			if(j>1&&j<=n) tj((i-1)*n+j-1,(i-1)*n+j,x);
		}
	heap[1]=s;
	pos[s]=1;
	for(i=1;i<=n*n;i++)
	{
		d[i]=INF;
		heap[i+1]=i;
		pos[i]=i+1;
	}
	d[t]=INF;
	node=heap[t]=pos[t]=t;
	for(i=1;i<t;i++)
	{
		x=heap[1];
		sc();
		for(j=first[x];j!=0;j=next[j])
			if(d[v[j]]>d[x]+w[j])
			{
				d[v[j]]=d[x]+w[j];
				tz(pos[v[j]]);
			}
	}
	printf("%lld",d[t]);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值