【转】poj 2104 K-th Number

二分查找 + 线段树
这里线段树就是query()函数。不懂线段树的童鞋先不要急,你们姑且把它看成是二分就行了。
二分在这道题中是很重要的思想. 先初始化, b[]数组排好序就是a[0][], x=0, y=n-1. 我们先取中间的数num=a[0][mid], where mid=(x+y)/2 来测试, 我们得到num在b[lef...rig]中的排在[rankL, rankR)的位置(注意是左闭右开区间). 如果
A. rankL<=k && k<rankR 那么数num就是所求的数; 否则根据下面更新x或y不断迭代.
B.k >= rankR 那么num较小, 也就是mid的位置较低, 于是取[x...y]区间的右边, 赋值x = mid+1;
C. y = mid; 那么num较大, 于是取[x...y]区间的左边, 赋值y = mid;
 
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
 
const int ex = 20;
const int maxn =100104;
// rankL是指示大于等于某数的最小位置, rankR是指示大于某数的最小位置
// 比如在数组(必须升序)1, 3, 3, 5, 6, 查找数3, 那么rankL=1, rankR=3; 查找数4, 那么rankL=rankR=3.
int n, m, lef, rig, k, rankL, rankR; // 在每个查询中, 查询区间b[lef...rig]的第k个数.
// a数组共有ex层, 在每一层内, 我们只对某一段进行排序. 看level_sort()函数对其进行理解.
int a[ex][maxn], b[maxn];
 
// 提示: 把level_sort改为归并排序可以节省时间
void level_sort(int x, int y, int dep) { // 表示在层dep中, 我们对a[dep][x...y]进行排序.
if (x == y) return; // 只有一个数, 直接返回; 否则我们需要深入下一层继续排序
sort(a[dep]+x, a[dep]+y+1);
int mid = (x+y) / 2;
level_sort(x, mid, dep+1);
level_sort(mid+1, y, dep+1);
}
 
// 预处理
void process() {
for (int i=0; i < ex; i++) {
memcpy(a[i], b, sizeof(int)*n);
}
level_sort(0, n-1, 0);
}
 
// binary search, 二分查找.
// 可以把它看成是两个函数合成一个, 用judge来判断.
// if (judge == 0), 那么就返回大于等于某数的最小位置(和rankL相关)
// if (judge == 1), 那么就返回大于某数的最小位置(和rankR相关)
int bin(int x, int y, int num, int dep, int judge) {
y++;
int ll = x;
while (y > x) {
int mid = (x+y) / 2;
if (a[dep][mid] - num >= judge) y = mid;
else x = mid+1;
}
return x-ll;
}
 
// 线段树, 相当于二分. 功能: 求数num在b[l...r]中的位置[rankL, rankR)
// 我们现在处于a[dep][ll...rr]中.
void query(int ll, int rr, int l, int r, int num, int dep) {
if (ll==l && rr==r) { // 因为a[dep][ll...rr]是排好序的, 而我们需要查找num在b[l(=ll)...r(=rr)]的位置, 所以就可以直接二分查找求位置了.
rankL += bin(ll, rr, num, dep, 0);
rankR += bin(ll, rr, num, dep, 1);
return;
}
// 否则, 我们需要深入到下一层迭代.
if (r<ll || l>rr) return;
int mid = (ll+rr) / 2;
query(ll, mid, l, min(mid, r), num, dep+1);
query(mid+1, rr, max(l, mid+1), r, num, dep+1);
}
 
void solve() {
int x = 0;
int y = n-1;
int mid = (x+y) / 2;
while (true) {
mid = (x+y) / 2;
rankL=0, rankR=0;
query(0, n-1, lef, rig, a[0][mid], 0); // 求数a[0][mid]的位置[rankL, rankR)
if (rankL<=k && k<rankR) break; // 数a[0][mid]就是所求的数
if (k >= rankR) x = mid+1; // a[0][mid]较小, 也就是mid的位置较低, 于是取[x...y]区间的右边, 继续迭代
else y = mid; // a[0][mid]较大, 也就是mid的位置较高, 于是取[x...y]区间的左边, 继续迭代
}
printf("%d\n", a[0][mid]);
}
 
int main()
{
scanf("%d%d", &n ,&m);
for (int i=0; i < n; i++) {
scanf("%d", &b[i]);
}
process();
while (m--) {
scanf("%d%d%d", &lef, &rig, &k);
lef--; rig--; k--;
solve();
}
return 0;
}
 
 

有一年多没发过博文了,刚到自己的博客里看,居然几天前还有访客。并且两个月前到现在的访问量还蛮稳定的,说明我写的东西还是能让一些读者感到有兴趣的(大部分应该是计算机专业的学生),同时希望大家能从我写的内容学到一些东西。:-)

至于为什么我在一年多后的今天再次写博文,是因为POJ 2104这道题有些触动到我,所以我想写下一些东西和大家分享。它触动我的原因首先是这道题很好,另外这道题让我调试了一天,而错误的地方是在solve()函数里,那个while(true)循环原本我写成是while(y > x)导致WA;读者可以自己思考错的原因是什么,并且欢迎你们把自己的想法写下来回复我。
 
posted @ 2012-08-01 12:24  何解一直犯相同错误?  阅读(124)  评论(0编辑  收藏  举报