前段时间校内测,出了一套水题,黑了俩学长,也学到了很多
其中一道题大致题意是这样的:在一个N*N的带权网格图中,找一条从左上角到右下角的路径(可以重复走),使路径上权值的最大值和最小值的差最小,求这个最小值,其中N≤100,点权≤3000
这是很久之前做的一道题的加强版,原题N≤100,点权≤110,最初做这题时,还是个没参加过NOIP的渣渣,连枚举答案+验证连通都想不到,那时候标算是二分答案+验证连通,是枚举最低点,二分最高点,再进行验证连通,复杂度是O(WlogW*N^2),W是点权,当时懵懵地懂了,第二次再碰到这题时,我写的也是二分答案+验证连通,只不过是先二分答案,再枚举最低点进行验证,当时体会了一下,觉得这样会优化下来很多,省去很多没有必要的步骤,大概知道啥意思,但是讲不清楚,和老师争论了一番,最后不了了之,这次出这道题原意是想按这个复杂度,点权可以扩大到1000,数据扩大后两个方法的差异会明显,然后测完之后发现自己这个跑得飞快,原本那个标算TLE~~,然后很兴奋的找神犇验题,神犇很不好意思地说:“我只会W*N^2的方法”然后就是妥妥地石化,后来和学长讨论了一下题目,又领会了一下,发现其实不需要二分枚举,因为两个端点的移动本来就是单调的,所以很显然对于端点的枚举可以O(N)进行实现,所以就是W*N^2了,为了卡常(有些无良),我把点权升到3000,1s时限,不开O2,然后考试时就出现了很有趣的一件事:学长们一直揪心卡常问题在优化,还有一个神犇学长自己内部开O2然后出了个神奇bug就100→0了,妥妥直播尴尬
不多说,贴代码
const flag:array[0..3,0..1]of longint=((-1,0),(0,1),(1,0),(0,-1));
var n,ans:longint;
vis:array[0..105,0..105]of boolean;
a:array[0..105,0..105]of longint;
procedure init;
var i,j:longint;
begin
assign(input,'mismatching.in');reset(input);
assign(output,'mismatching.out');rewrite(output);
readln(n);ans:=0;
for i:=1 to n do
begin
for j:=1 to n do
begin
read(a[i,j]);
if a[i,j]>ans then ans:=a[i,j];
end;
readln;
end;
end;
function check(x,y:longint):boolean;
begin
if (x<1)or(y<1)or(x>n)or(y>n) then exit(false);
if vis[x,y] then exit(false);
exit(true);
end;
procedure dfs(minh,maxh,x,y:longint);
var i,xx,yy:longint;
begin
vis[x,y]:=true;
for i:=0 to 3 do
if check(x+flag[i,0],y+flag[i,1]) then
begin
xx:=x+flag[i,0];
yy:=y+flag[i,1];
if (a[xx,yy]>=minh)and(a[xx,yy]<=maxh) then dfs(minh,maxh,xx,yy);
end;
end;
procedure main;
var i,j,L,R,bo,h0,h1,mid:longint;
begin
L:=ans;R:=ans;
while (L>=0)and(L<=R) do
begin
bo:=0;
if (a[1,1]>=L)and(a[1,1]<=R) then begin
fillchar(vis,sizeof(vis),0);
dfs(L,R,1,1);
if vis[n,n] then begin
if R-L<ans then ans:=R-L;
bo:=1;
end;
end;
if bo=0 then dec(L)
else dec(R);
while not(L<=R) do dec(L);
end;
end;
procedure print;
begin
writeln(ans);
close(input);close(output);
end;
begin
init;
main;
print;
end.
#写得比较LOW,不喜勿喷#
最近理了一下思路,发现之前写的那个在推左右端点的时候有些拖拉,也比较乱,所以虽然理论上复杂度O(n)的推法,但是常数相当大,在某些时候复杂度接近O(nlogn),甚至在某些特殊情况下跑得比O(nlogn)还慢#这就比较尴尬了#,这几天做了一道单调栈维护优化的题,题目传送门 ,突然想的比较清楚,同样的方法又推了一次,左右端点都从1开始推,常数比较小,写起来也比较清楚,代码见下
贴代码
#include<cstdio>
#include<cstring>
using namespace std;
int const maxn=105;
int a[maxn][maxn];
bool vis[maxn][maxn];
int flag[4][2]={{-1,0},{0,-1},{1,0},{0,1}};
int n;
char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
void read(int &x){
char ch=nc();int ff=1,res=0;
while (!('0'<=ch&&ch<='9'))ch=='-'?ff=-1:0,ch=nc();
while ('0'<=ch&&ch<='9')res=(res<<3)+(res<<1)+ch-'0',ch=nc();
x=ff*res;
}
bool check(int x,int y){
if(x<1||y<1||x>n||y>n)return 0;
if (vis[x][y])return 0;
return 1;
}
void dfs(int minh,int maxh,int x,int y){
vis[x][y]=1;
for (int i=0;i<4;i++)
if (check(x+flag[i][0],y+flag[i][1])){
int xx=x+flag[i][0];
int yy=y+flag[i][1];
if(minh<=a[xx][yy]&&a[xx][yy]<=maxh)dfs(minh,maxh,xx,yy);
}
}
int main(){
freopen("mismatching.in","r",stdin);
freopen("mismatching.out","w",stdout);
read(n);int max=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++){
read(a[i][j]);
max=a[i][j]>max?a[i][j]:max;
}
int L,R;
L=R=0;
int ans=max;
for (int R=0;R<=max;R++){
int bo=0;
while (R-L>=ans)L++;
while (L<=R){
if (!(L<=a[1][1]&&a[1][1]<=R))break;
if (!(L<=a[n][n]&&a[n][n]<=R))break;
memset(vis,0,sizeof(vis));
dfs(L,R,1,1);
if (vis[n][n])L++,bo=1;
else break;
}
if (bo==1)ans=R-L+1<ans?R-L+1:ans;
}
printf("%d",ans);
return 0;
}
2016/11/2 23:37:10
【写的有漏洞的,欢迎路过大神吐槽】
Ending.