一道黑人的题

2 篇文章 0 订阅
1 篇文章 0 订阅

  前段时间校内测,出了一套水题,黑了俩学长,也学到了很多

  其中一道题大致题意是这样的:在一个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.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值