[NOIP2010 提高组] 引水入城

First

  我们首先可以通过bfs判断是否能满足要求

  此时这题就变成了:在一定能够满足要求的情况下,最少建造的蓄水厂数量。

  这有什么区别呢?还真有

  为了方便描述,我们约定,每一个与湖泊毗邻的点叫做源点,靠近沙漠的点叫做汇点。

  它导致了这样一个结论:

任意一个点其能够到达的汇点的一定构成连续的一段区间

  首先我们要想到,对于从某一个源点出发一直流到它能到达的所有汇点的路径,该路径不可达的左端和右端相互不可达。

  这个应该比较显然,因为其左端不能够跨过这条路径到右端,而由于其是从源点到汇点,所以也不能绕过它流到右边

  考虑一个点,我们取它能到达的任意两汇点,若此两点间有一汇点无法被该点覆盖,则任何源点都到不了这个汇点。此时的点不一定是汇点,但由于其大致形成了一种“两面包夹之势”,所以可以扩展一下。

在这里插入图片描述

  所以我们大概就浅浅的证明了最初的那个结论,于是每一个源点所能到达的汇点就构成一段区间。

  此时这道题就变成了下面这两个子问题:如何在时限内求出各个源点所能到达的汇点区间?如何用最少的区间覆盖1~m的所有点?

--------------------------------------------------------分割线--------------------------------------------------------

Second

第一个问题,其实如果对于图上每一个点,从它向其能到的邻点连边,这就构成了一个DAG(有向无环图)。我们要利用的其实就是这个“无环”,对于一个点,如果它能到达的汇点区间为[l,r],则能够流到它的点的汇点区间一定包含[l,r],于是我们可以进行记忆化搜索,搜到一个新的点之后,回溯时用其更新上一个点。

第二个问题,我们可以贪心。首先我们想到1一定要被覆盖的话,我们肯定要取一个左端点最靠左的区间。于是想到以左端点为第一关键字从小到大排序,那第二关键字呢?此时可以先简单的贪心一下,在左端点相同的情况下,大区间一定更优,所以是以右端点为第二关键字从大到小排序。

然后我们可以想象一下,我们从左至右遍历1~m,如果此时的点被覆盖了,那我们还不急。当一个点k没被覆盖时,我们就要用某个区间去覆盖它。能用的区间是哪些呢?肯定只有左端点不比x大的才可以,由于我们之前按照左端点排序过,所以这些区间的下标全是从1开始连续的,我们想要蓄水厂最少,那我们不急的次数就要最多(急了的次数最少),于是我们就要取这些区间中右端点最大的(根据可行性,它一定不小于k)。而且由于我们取得是右端点最大的,它也一定没被取过。

综上,这道题就做完啦!在我看来,最关键的还是最开始的那个结论,如果我们按题目要求先做第一问的话,可能能更快想到这个结论。当然,也有一个不太严谨的证明想法:如果不是这样,那就基本上要状压了,所以不可能。

Last

第一次交WA了一发,是因为for循环的时候习惯性的写的1~n

code

#include<cstdio>
#include<cctype>
#include<algorithm>
#include<queue>
using namespace std;
#define For(i,a,b) for(int i=(a);i<=(b);++i)
#define rep(i,u,x,H) for(int i=H[u];i;i=x[i].nxt)
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
#define N 505
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
int n,m;
int b[N][N],vis[N];
int h[N][N];
int l[N][N],r[N][N];
struct qnode{int x,y;};
struct node{
	int l,r;
	friend bool operator<(const node n1,const node n2){
		if(n1.l==n2.l)return n1.r>n2.r;
		return n1.l<n2.l;
	}
}a[N];
inline int read(){
  char ch=getchar();int x=0,f=1;
  for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
  for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
  return x*f;
}
void bfs(){
	queue<qnode>q;
	For(i,1,m){
		q.push({1,i});
		b[1][i]=1;
	}
	while(!q.empty()){
		qnode head=q.front();q.pop();
		int x=head.x,y=head.y;
		if(x==n)vis[y]=1;
		For(i,0,3){
			int tx=x+dx[i],ty=y+dy[i];
			if(tx<1||tx>n||ty<1||ty>m||h[tx][ty]>=h[x][y])continue;
			if(!b[tx][ty]){
				b[tx][ty]=1;
				q.push({tx,ty});
			}
		}
	}
}
void dfs(int x,int y){
	if(x==n){
		l[x][y]=r[x][y]=y;
	}
	For(i,0,3){
		int tx=x+dx[i],ty=y+dy[i];
		if(tx<1||tx>n||ty<1||ty>m||h[tx][ty]>=h[x][y])continue;
		if(!b[tx][ty]){
			b[tx][ty]=1;
			dfs(tx,ty);
		}
		l[x][y]=Min(l[x][y],l[tx][ty]);
		r[x][y]=Max(r[x][y],r[tx][ty]);
	}
}
int main(){
	n=read(),m=read();
	For(i,1,n){
		For(j,1,m)h[i][j]=read();
	}
	bfs();
	int ans=0;
	For(i,1,m)if(!vis[i]){
		++ans;
	}
	if(ans){
		puts("0");
		printf("%d\n",ans);
		return 0;
	}
	For(i,1,n)For(j,1,m)b[i][j]=0,l[i][j]=m+1,r[i][j]=0;
	For(i,1,m)if(!b[1][i])b[1][i]=1,dfs(1,i);
	For(i,1,m){
		a[i]={l[1][i],r[1][i]};
	}
	sort(a+1,a+1+m);
	int k=a[1].r,mx=0;ans=1;
	For(i,2,m){
		if(k<a[i].l-1)k=mx,++ans;
		mx=Max(mx,a[i].r);
	}
	if(k<m)k=mx,++ans;
	printf("1\n%d\n",ans);
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值