【NOIP2010提高组】引水入城

20 篇文章 0 订阅
6 篇文章 0 订阅

【问题描述】



    经典的难题一道,考试的时候实在是鱼,连搜素都没有想到,吃惊的是搜索加点剪枝能过6个点。。。。。。

    前3个点很简单,以所有第一行的点为起点多源BFS,看能不能把最后一排的格子都遍历到。

    第4个点开始,就必须要用搜索了,可以搜起点行的子集,然后从选择的点当中多源BFS一次看能不能把最后一排走完,记录最少的选择数。这里还可以加剪枝,只搜到上次最少的选择数即可。运气好能过到6个点。

    然而这个图很大,搜索终究不是一个办法,观察这个图,其实有一个很重要的性质,假如能达到题目要求,就是第一行的点能遍历到的最后一行的点必然是连续的。以下简单证明一下:

    用反证法,如果不连续,则必然在最后一行有一点的海拔比相邻两个点都要高。

    那么水不能从相邻的两个点流到这个点,而且也不能这个点正上方的那个点流下来,也就是说,这个点被三面堵死了,不可能再有其他起点的水能够流到这个格子,不能满足题目要求,与假设矛盾。

    所以第一行的点的水能流到最后一行的格子必然是一段区间,贪心用区间覆盖[1,j]的最少区间数就是答案。

    

#include<cstdio>
#include<cstdlib>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#include<iostream>
#define maxn 505
using namespace std;
int n,m;
int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};
int a[maxn][maxn];
int vis[maxn][maxn];
struct data
{
	int x,y;
};
data q[maxn*maxn];
int front,rear;
struct qj
{
	int x,y;
}p[maxn];
void bfs(int sx,int sy,int c)
{
	front=rear=1;
	q[rear++]=(data){sx,sy};
	vis[sx][sy]=c;
	
	while(front!=rear)
	{
		data t=q[front++];
		for(int k=0;k<4;k++)
		{
			int ex=t.x+dx[k];
			int ey=t.y+dy[k];
			if(ex<=0 || ex>n || ey<=0 || ey>m)continue;
			if(a[t.x][t.y]<=a[ex][ey])continue;
			if(vis[ex][ey]==c)continue;
			q[rear++]=(data){ex,ey};
			vis[ex][ey]=c;
		}
	}
}

int main()
{
	//freopen("flow.in","r",stdin);
	//freopen("flow.out","w",stdout);

	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	scanf("%d",&a[i][j]);
	
	int cc=0;
	for(int j=1;j<=m;j++)
	{
		bfs(1,j,++cc);
		for(int k=1;k<=m;k++)if(vis[n][k]==cc)
		{
			p[j].x=k;
			break;
		}
		for(int k=1;k<=m;k++)if(vis[n][k]==cc)
		{
			p[j].y=k;
		}
	}
	
	int cnt=0;
	for(int j=1;j<=m;j++)if(!vis[n][j])
	{
		cnt++;
	}
	
	if(cnt>0)
	{
		printf("0\n%d\n",cnt);	
	}
	
	else
	{
		int i=1,num=0,s=1,t=m;
		while(i<=m && s<=t)
		{
			int last=-1;
			while(i<=m && p[i].x<=s)
			{
				last=max(last,p[i].y);
				i++;
			}
			if(last<s)break;
			num++;
			s=last+1;
		}
		
		printf("1\n%d\n",num);
	}
	
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值