HDU 3648 Median Filter



弱渣花了好几天终于把这题A了_(:з」∠)_

可能因为大牛们认为这题太简单吧,网上的题解都不怎么带注释。特别是那个S型走法,看的博(ruo)主(cai)一愣一愣的_(:з」∠)_

因此AC后打算整理下大牛们的题解并加上注释,以便查阅_(:з」∠)_

主要参考了下面几篇题解:

http://blog.csdn.net/zhaofukai/article/details/7841327

http://blog.csdn.net/chromer_cn/article/details/7892048


原题:http://acm.hdu.edu.cn/showproblem.php?pid=3648

题意:给出一个n*n的矩阵,现在对于(r+1, r+1) to (n-r, n-r)中每个点,以其为中心的规模为(2r+1)*(2r+1)的子矩阵,替换其中间元素为该子矩阵的中位数。

n<=500 元素<=10^6。


A这道题前首先要知道什么是树状数组和怎么样用树状数组快速求第k小的值。

博(ruo)主(cai)从书上学的树状数组,在这就不安利书籍了,网上也有很多很棒的资料。

至于怎么样用树状数组求第k小的值,博(ruo)主(cai)个人认为这篇讲的挺好的 http://www.cnblogs.com/wuyiqi/archive/2011/12/25/2301071.html 

看的时候注意树状数组[1...i]的区间和表示的是“树状数组中小于等于i的数的个数和”就行了。(防止有新手误解,强调一下上面的“区间和”指的是用树状数组求出来的区间和,而不是简单的从下标1开始将每个元素相加知道下标i !!!)


准备好了知识点,就可以做题了。

实际上思路很好想的嘛_(:з」∠)_,将子阵中的数插入到树状数组求中位数,然后再插几行删几行求新子阵的新中位数。不过听说普通的走法会TLE,走S型路线可以最大限度的利用旧子阵,然后就AC了_(:з」∠)_。


有人可能会问k的范围怎么定,这种问题自己画个子阵就不清楚了嘛...


#include<bits/stdc++.h>
#define maxv 1000010 
#define maxn 510
using namespace std;

int N,R,mxv;
int table[maxn][maxn],ans[maxn][maxn];

int bit[maxv];//改了一下的树状数组模板,没什么好说的吧?
inline int lowbit(int x){
	return x&-x;
}
void add(int i,int x){
	while(i<=mxv){
		bit[i]+=x;
		i+=lowbit(i);
	}
}
int find_kth(int k){
	int ret=0,cnt=0;
	for(int i=20;i>=0;i--){
		ret+=(1<<i);
		if(ret>=mxv||cnt+bit[ret]>=k) ret-=(1<<i);
		else cnt+=bit[ret];
	}
	return ret+1;
}

void SOLVE()
{
	int mid=2*R*R+2*R+1;//子阵的中位数是子阵中第mid小的数
	
	bool pass=true;//S型走法中向右走的那些行
	for(int i=R+1;i<=N-R;i++)
		if(pass){//向右走
			pass=false;
		
			for(int j=R+1;j<=N-R;j++){
				if(j==R+1)//第一格特殊处理,即往下走,删旧的一行加新的一行
					for(int k=j-R;k<=j+R;k++)
						add(table[i-R-1][k],-1),
						add(table[i+R][k],1);
				else//向右走,删旧的一列加新的一列
					for(int k=i-R;k<=i+R;k++)
						add(table[k][j-R-1],-1),
						add(table[k][j+R],1);
				
				ans[i][j]=find_kth(mid);//求中位数
			}					
		}else{//向左走
			pass=true;
			
			for(int j=N-R;j>=R+1;j--){
				if(j==N-R)//第一格特殊处理,即往下走,删旧的一行加新的一行
					for(int k=j-R;k<=j+R;k++)
						add(table[i-R-1][k],-1),
						add(table[i+R][k],1);
				else //向左走,删旧的一列加新的一列
					for(int k=i-R;k<=i+R;k++)
						add(table[k][j+R+1],-1),
						add(table[k][j-R],1);
				
				ans[i][j]=find_kth(mid);//求中位数
			}								
		}
		
	for(int i=R+1;i<=N-R;i++)
		for(int j=R+1;j<=N-R;j++)
			printf("%d%s",--ans[i][j],j==N-R?" \n":" ");
}

bool INPUT()
{
	scanf("%d%d",&N,&R);
	if(!N&&!R) return false;
	
	mxv=-1;
	for(int i=1;i<=N;i++)
		for(int j=1;j<=N;j++)
			scanf("%d",&table[i][j]),
			mxv=max(mxv,++table[i][j]);//0的lowbit还是0,自然没办法插入,处理一下就可以了(*)
	
	memset(bit,0,sizeof bit);
	//插入第一个子阵,不过为了SOLVE中的走法统一,这里插入的是第一个子阵的上面一格的那个子阵
	for(int i=0;i<=(R<<1);i++)
		for(int j=1;j<=(R<<1)+1;j++)
			add(table[i][j],1);
			
	return true;
}

void MAIN()
{
	for(int i=0;i<maxn;i++) table[0][i]=1;//和(*)一样的道理
	
	while(INPUT())
		SOLVE();
}

int main()
{
	//freopen("in#pro.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	MAIN();
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值