[NOI2010] 海拔altitude 最小割 对偶图

  观察题目,总的代价是由各个节点的海拔决定的,而s与t都已事先确定好了(废话,不然玩什么),我们需要做的就是确定各个点的海拔。

  思考后发现,由左上到右下海拔的趋势是逐渐升高的,不会出现高-->低-->高,或者低-->高-->的情况,因为这样会支付两边来人的钱,得不偿失。

  继续观察,发现由a到b,如果海拔增高就一定会一次性从0增到1,因为当出现h[a]<h[b]<h[c] 逐渐升高的情况时,假设w[a,b]>w[b,c],则这样设置海拔的代价会大于h[a]==h[b]==0,h[c]=1的情况。

  所以图中节点的海拔非0即1,且左上那一团为0,右下那一团为1。(可见他给的四舍五入,保留到整数有多坑大笑)

  思考到这里我们就发现,我们要寻找的就是那一条01节点接壤的线,而这条线正好将这些点分为了两个集合,s与t也恰好在这两个集合,so,瞬间想到最小割。

  然而存在20%的数据图中有250000个点,这么多点用网络流坑定会超时,有想到这个图是一个规则的图,再联想到bzoj1001狼抓兔子,可以把最小割转化为对偶图的最短路,而最短路不论是spfa还是heap_dij都可以在2s内跑完。

  注:如何转化为对偶图比较简单,很容易YY出来。

有一点太坑了,要注意转化为对偶图之后边的数量,我wa了两次,不晓得为什么,还是把数据下下来才反应过来的,太2了,虐杀呀。

#include <cstdio> 
#include <cstring> 
#include <cstdlib> 
#include <cmath> 
#include <ctime> 
#include <iostream> 
#include <algorithm>

using namespace std;
const int MAX=1000000;
int n;
int s,t;

int tot=1;
int fir[300000],en[MAX*2],nex[MAX*2],w[MAX*2];
inline void ins(int a,int b,int c){
//	printf("%d %d %d\n",a,b,c);
	nex[++tot]=fir[a];
	fir[a]=tot;
	en[tot]=b;
	w[tot]=c;
}

int dis[300000];
int sta[MAX*24+10],v[300000];

inline void spfa(){
	memset(dis,31,sizeof(dis));
	dis[s]=0;
	int head=0,tair=1;
	sta[1]=s;v[s]=1;
	while (head<tair){
		head++;
//		if (head>=MAX*10) head=1;
		for (int k=fir[sta[head]];k;k=nex[k])
			if (dis[en[k]]>dis[sta[head]]+w[k]){
				dis[en[k]]=dis[sta[head]]+w[k];
				if (!v[en[k]]){
					tair++;
//					if (tair>=MAX*10) tair=1;
					sta[tair]=en[k];
					v[en[k]]=1;
					}
				}
		v[sta[head]]=0;
		}

}


int main(){
//	freopen("altitude.in","r",stdin);
//	freopen("altitude.out","w",stdout);
	scanf("%d",&n);
	s=0;t=n*n+1;
	
	//西to东 
	for (int j=1;j<=n;j++){
		int x;
		scanf("%d",&x);
		ins(j,t,x);
		}
	for (int i=2;i<=n;i++)
		for (int j=1;j<=n;j++){
			int x;
			scanf("%d",&x);
			ins((i-1)*n+j,(i-2)*n+j,x);
			}
	for (int j=1;j<=n;j++){
		int x;
		scanf("%d",&x);
		ins(s,n*(n-1)+j,x);
		}
	
		
	//北to南
	for (int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		ins(s,(i-1)*n+1,x);
		for (int j=2;j<=n;j++){
			scanf("%d",&x);
			ins((i-1)*n+j-1,(i-1)*n+j,x);
			}
		
		scanf("%d",&x);
		ins(i*n,t,x);
		}
	

	
	
	//东to西
	for (int j=1;j<=n;j++){
		int x;
		scanf("%d",&x);
		ins(t,j,x);
		}
	for (int i=2;i<=n;i++)
		for (int j=1;j<=n;j++){
			int x;
			scanf("%d",&x);
			ins((i-2)*n+j,(i-1)*n+j,x);
			}
	for (int j=1;j<=n;j++){
		int x;
		scanf("%d",&x);
		ins(n*(n-1)+j,s,x);
		}
	
	 
	//南to北 


	for (int i=1;i<=n;i++){
		int x;
		scanf("%d",&x);
		ins((i-1)*n+1,s,x);
		for (int j=2;j<=n;j++){
			scanf("%d",&x);
			ins((i-1)*n+j,(i-1)*n+j-1,x);
			}
		
		scanf("%d",&x);
		ins(t,i*n,x);
		}
	
	spfa();
	
	printf("%d",dis[t]);

	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值