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;
}