BZOJ 1001[BeiJing2006]狼抓兔子 最小割转最短路

Description

现在小朋友们最喜欢的"喜羊羊与灰太狼",话说灰太狼抓羊不到,但抓兔子还是比较在行的,
而且现在的兔子还比较笨,它们只有两个窝,现在你做为狼王,面对下面这样一个网格的地形:

 

左上角点为(1,1),右下角点为(N,M)(上图中N=4,M=5).有以下三种类型的道路 
1:(x,y)<==>(x+1,y) 
2:(x,y)<==>(x,y+1) 
3:(x,y)<==>(x+1,y+1) 
道路上的权值表示这条路上最多能够通过的兔子数,道路是无向的. 左上角和右下角为兔子的两个窝,
开始时所有的兔子都聚集在左上角(1,1)的窝里,现在它们要跑到右下解(N,M)的窝中去,狼王开始伏击
这些兔子.当然为了保险起见,如果一条道路上最多通过的兔子数为K,狼王需要安排同样数量的K只狼,
才能完全封锁这条道路,你需要帮助狼王安排一个伏击方案,使得在将兔子一网打尽的前提下,参与的
狼的数量要最小。因为狼还要去找喜羊羊麻烦.

Input

第一行为N,M.表示网格的大小,N,M均小于等于1000.
接下来分三部分
第一部分共N行,每行M-1个数,表示横向道路的权值. 
第二部分共N-1行,每行M个数,表示纵向道路的权值. 
第三部分共N-1行,每行M-1个数,表示斜向道路的权值. 
输入文件保证不超过10M

Output

输出一个整数,表示参与伏击的狼的最小数量.

Sample Input

3 4
5 6 4
4 3 1
7 5 3
5 6 7 8
8 7 6 5
5 5 5
6 6 6

Sample Output

14

HINT

 2015.4.16新加数据一组,可能会卡掉从前可以过的程序。


这题第一眼看到觉得是最大流问题。。但是数据很大,不太对?(其实是我不熟悉网络流)

然后的话,这题实际上可以看作一个最短路问题。

推荐论文:两极相通——浅谈最大—最小定理在信息学竞赛中的应 周冬

大概说一下这个例子是如何建图的。

然后我们看到这张图里面分了很多三角块,从左上角的左下三角开始,

标号1,然后第一个矩阵的右上标为2.......大概这样:

然后原点可以看作13,重点看作14.

两个点之间的路径,其实就是通过的边的边权。

比如说8~9,长度是7,5~6是5等等。

至于13和14图片中没有标明,13在左下角,14在右上角。

这两个是原点和终点,我们的目标就是从原点走到终点。

原点和网格图的最左/最下的块相连。比如13连着1,7,9,11。

同理14连着2,4,6,12.

还有一些就是相邻的一些点(如8和7,9,1)他们之间也需要连一条边。

建图的过程有点小麻烦。。不过思考一下就能想通了。

(add的过程是边表加边,由于无向边,所以加两次。heng[][]这种是题目读入的数据)

void build(){
	N=(n-1)*(m-1)<<1;
	Ecnt=0;
	for (int i=1;i<=N;i++){
		int a=i/((m-1)<<1),b=i%((m-1)<<1);
		if (i&1){
			add(i,i+1,xie[a+1][(b>>1)+1]);
			add(i+1,i,xie[a+1][(b>>1)+1]);
			if (i+((m-1)<<1)+1<=N)
				 add(i,i+((m-1)<<1)+1,heng[a+2][(b>>1)+1]),
				 add(i+((m-1)<<1)+1,i,heng[a+2][(b>>1)+1]);
		}
			else
			if (b) add(i,i+1,shu[a+1][(b>>1)+1]),
				   add(i+1,i,shu[a+1][(b>>1)+1]);
	}
	int tmp; //上面是分出的块内部的关系,下面处理最周边一圈和原点以及终点的关系
	start=N+1; end=N+2;
	for (int i=1;i<=N;i+=(m-1)<<1){
		int a=i/((m-1)<<1);
		add(start,i,shu[a+1][1]);
		add(i,start,shu[a+1][1]);
		tmp=i;
	}
	for (int i=tmp;i<=N;i+=2){
		int a=i%((m-1)<<1);
		add(start,i,heng[n][(a>>1)+1]);
		add(i,start,heng[n][(a>>1)+1]);
		tmp=i;
	}
	tmp++;
	for (int i=tmp;i;i-=(m-1)<<1){
		int a=i/((m-1)<<1);
		add(end,i,shu[a][m]);
		add(i,end,shu[a][m]);
		tmp=i;
	}
	for (int i=tmp;i;i-=2){
		add(end,i,heng[1][i>>1]);
		add(i,end,heng[1][i>>1]);
	}
}



然后建图完毕后就可以直接跑最短路了。

由于有(n-1)*(m-1)*2+2个节点,2000000,所以可用SPFA或者dij+优先队列。

这个最短路的部分就不说了。。直接跑。。

注意了,此题有个坑。。我掉进去好久。

当n=1 || m=1,我的程序会出点错(由于我建图的打法)

所以这个时候特判就好了。


#include<bits/stdc++.h>
using namespace std;
int n,m,N,Ecnt,dis[6000005];
bool vis[6000005];
int start,end;
struct Edge{
	int next,to,val;
}ty[6000005];  int head[6000005];
struct Heap{
	int num,val;
};
#define qq(a,b) (Heap){a,b} 
priority_queue<Heap,vector<Heap> >Q;
void add(int u,int v,int w){
	ty[++Ecnt].next=head[u];
	ty[Ecnt].to=v;
	ty[Ecnt].val=w;
	head[u]=Ecnt;
}
int heng[1005][1005],shu[1005][1005],xie[1005][1005];
void tyread(){
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		for (int j=1;j<m;j++) scanf("%d",&heng[i][j]);
	for (int i=1;i<n;i++)
		for (int j=1;j<=m;j++) scanf("%d",&shu[i][j]);
	for (int i=1;i<n;i++)
		for (int j=1;j<m;j++) scanf("%d",&xie[i][j]);
}
void build(){
	N=(n-1)*(m-1)<<1;
	Ecnt=0;
	for (int i=1;i<=N;i++){
		int a=i/((m-1)<<1),b=i%((m-1)<<1);
		if (i&1){
			add(i,i+1,xie[a+1][(b>>1)+1]);
			add(i+1,i,xie[a+1][(b>>1)+1]);
			if (i+((m-1)<<1)+1<=N)
				 add(i,i+((m-1)<<1)+1,heng[a+2][(b>>1)+1]),
				 add(i+((m-1)<<1)+1,i,heng[a+2][(b>>1)+1]);
		}
			else
			if (b) add(i,i+1,shu[a+1][(b>>1)+1]),
				   add(i+1,i,shu[a+1][(b>>1)+1]);
	}
	int tmp;
	start=N+1; end=N+2;
	for (int i=1;i<=N;i+=(m-1)<<1){
		int a=i/((m-1)<<1);
		add(start,i,shu[a+1][1]);
		add(i,start,shu[a+1][1]);
		tmp=i;
	}
	for (int i=tmp;i<=N;i+=2){
		int a=i%((m-1)<<1);
		add(start,i,heng[n][(a>>1)+1]);
		add(i,start,heng[n][(a>>1)+1]);
		tmp=i;
	}
	tmp++;
	for (int i=tmp;i;i-=(m-1)<<1){
		int a=i/((m-1)<<1);
		add(end,i,shu[a][m]);
		add(i,end,shu[a][m]);
		tmp=i;
	}
	for (int i=tmp;i;i-=2){
		add(end,i,heng[1][i>>1]);
		add(i,end,heng[1][i>>1]);
	}
}
bool operator<(Heap x,Heap y){
	return x.val>y.val;
}
void dijkstra(int s,int t){
	memset(vis,0,sizeof(vis));
	memset(dis,60,sizeof(dis));
	dis[s]=0;
	Q.push(qq(s,0));
	while ((!Q.empty())){
		Heap mi=Q.top();
		Q.pop();
		if (vis[mi.num]) continue;
		vis[mi.num]=1;
		for (int j=head[mi.num];j;j=ty[j].next){
			int q=ty[j].to;
			if (vis[q]) continue;
			int w=ty[j].val;
			if (w+mi.val<dis[q]){
				dis[q]=w+mi.val;
				Q.push(qq(q,dis[q]));
			}
		}
	}
}
int main(){
	tyread();
	if (n==1 || m==1){
		int ans=1e9;
		if (n==1){
			for (int j=1;j<m;j++)
				ans=min(ans,heng[1][j]);
			printf("%d\n",ans);
		}	else{
			for (int i=1;i<n;i++)
				ans=min(ans,shu[i][1]);
			printf("%d\n",ans);
		}
		return 0;
	}
	build();
	dijkstra(start,end);
	printf("%d\n",dis[end]);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值