这个题目是我出给HDOJ的周年纪念赛的。不过好像难了点,居然几乎没有人提交。。
具体做法也确实不算简单。
大体方向是先做三角剖分,再用最小生成树的做法求解答案。
但是三角剖分之后需要一个数据结构来支持查询一个点在哪个三角形内。所以网上流行的分治法求三角剖分的标程是不行的。。^.^
具体做法有点复杂,我也懒的在这里细细说了。。
因为是给别人出的题目,所以标程和数据也不方便公布。有需要标程和数据的就给我发邮件吧:)
下面是我N年前写的东西了。。最近突然很多人问我这个问题,不知道是不是因为天津赛区的原因。。
原题就是给一个数组,然后询问一段区间内的第K大数。。。
发信人: mmd (mmd), 信区: ACM_ICPC
标 题: F题解题报告
发信站: 珞珈山水BBS站 (Tue Apr 1 12:10:07 2008), 转信
解题报告:
F题:
先说一下n log(n)预处理, log(n) ^ 3查询的方法
线段树中的节点不是存的点, 是存一条线段.(也就是很多点, 个数是这条线段的长度)
线段树中的节点是有序的, 序是按key排的.
结构跟归并排序里面的一样的.
这样可以用合并两个有序序列, 来建树, 很明显, 建树的复杂度是n log(n)的.
这样的一个数据结构, 很明显我们是可以查找一个区间中的一个数在有序表中的哪个位
置.
我们如果要找第k个数话, 便可以对这个数进行二分, 然后在区间中查找.
这样总体复杂度应该是n log(n) + log(n) ^ 3
这个方法很明显是非常慢的,.
因为首先要有一个二分, 这个复杂度就比较高, 因为数据是可以无限大的(可以通过离散
化来减小数据范围, 但显的不够优美)
第二要在线段树中对线段进行分割, 麻烦, 常数也比较大.
第三这个线段树的实现比较麻烦, 代码较长, 也容易错.
下面说一下n log(n) + log(n)的方法
这个方法, 与上面所说的方法在数据结构上要有所改变
1. 线段树也是存的一条线段(很多点)
2. 线段树也是有序的, 但是是按position有序的(按在原来数组中的位置有序)
3. 线段树中左孩子节点的key要比右孩子的key小
想清楚这个数据结构后, 可以想一下如何在这种数据结构上进行操作
上面这种结构非常巧妙, 是我无意中看一个外国人说的, 看到这个结构后, 我加了以下
几个简单附加结构, 就可以对这个问题比较完美的处理了.
1.a[n], a[i]表示从1 到 i中有多少个节点在左子树
通过这个数组, 我们可以计算出一个区间s – t中有多少个节点在左子树, 有多少个节点
在右子树, 为什么有这个东西呢?
有了这个东西, 我们就可以对我们要查找的区间进行递归了, 并且每次只会递归到一层
中, 所以复杂度是log(n)的
有了这个东西, 问题就基本解决了.
但我们递归的时候, 区间是变化的, 我们应该如何去应对这个改变呢?
比较简单的方法就是进行一次二分, 但是这样复杂度就变成log(n) ^ 2的.
为了解决这个问题, 我再次对区间进行了记录. 也就是对区间记录一个映射, 记录这个
区间在左孩子中所对应的区间和在右孩子中所对应的区间.
这些记录, 都可以能过合并两个有序序列的方法来建树, 都是n log(n)的.
说到这里, 这个问题就比较完美的解决了, 这个方法常数比较小, 又是单层递归, 可以
写成迭代形式.
参考代码:
//author: momodi@whuacm
class Tree {
public:
static const int maxn = 100000;
struct Seg {
int s, t;
struct Acc {
int b, c, l, r;
};
Acc *acc;
}seg[maxn * 3];
int n;
int *dt;
int id[maxn];
void init(int _n, int *_dt) {
n = _n;
dt = _dt;
for (int i = 0; i < n; ++i) {
id[i] = i;
}
sort(id, id + n, cmp(dt));
build(0, n, 0);
}
int find(int s, int t, int k, int v = 0) {
if (s + 1 == t) {
return dt[seg[v].acc[s].b];
}
int tmp = seg[v].acc[t - 1].c - ((s > 0)? seg[v].acc[s - 1].c: 0);
if (k <= tmp) {
return find(seg[v].acc[s].l, seg[v].acc[t].l, k, v * 2 + 1);
}else {
return find(seg[v].acc[s].r, seg[v].acc[t].r, k - tmp, v * 2 + 2);
}
}
private:
struct cmp {
int *dt;
cmp(int *_dt):dt(_dt){};
bool operator () (const int &a, const int &b) {
return dt[a] < dt[b];
}
};
void build(int s, int t, int v) {
seg[v].s = s;
seg[v].t = t;
if (s + 1 == t) {
delete seg[v].acc;
seg[v].acc = new Seg::Acc[2];
seg[v].acc[0].b = id[s];
return ;
}
int m = (s + t + 1) / 2;
build(s, m, v * 2 + 1);
build(m, t, v * 2 + 2);
merge(seg[v].acc, seg[v * 2 + 1].acc, seg[v * 2 + 2].acc, t - s, m - s, t - m);
}
void merge(Seg::Acc *&d, const Seg::Acc *l, const Seg::Acc *r, int n, int ln, int rn) {
delete d;
d = new Seg::Acc[n + 1];
d[n].l = ln;
d[n].r = rn;
for (int i = 0, li = 0, ri = 0; i < n; ++i) {
if (ri == rn || li < ln && l[li].b < r[ri].b) {
d[i].c = ((i > 0)? d[i - 1].c + 1: 1);
d[i].l = li;
d[i].r = ri;
d[i].b = l[li++].b;
}else {
d[i].c = ((i > 0)? d[i - 1].c: 0);
d[i].l = li;
d[i].r = ri;
d[i].b = r[ri++].b;
}
}
}
};
Tree tree;
const int maxn = 100000;
int dt[maxn];
int main()
{
int n, m;
while (scanf("%d %d", &n, &m) == 2) {
for (int i = 0; i < n; ++i) {
scanf("%d", dt + i);
}
tree.init(n, dt);
while (m--) {
int s, t, k;
scanf("%d %d %d", &s, &t, &k);
printf("%d\n", tree.find(s - 1, t, k));
}
}
return 0;
}
Open是武大长期流传下来的人文精神,也是我们集训队一直的宝贵财富。因此我们在Legend的基础上取名OpenLegend,作为本次比赛的队名。
比赛前一天:
本来是想周六中午BG大家的,结果发现了浙江理工在下沙之后就知道中午来不及BG了,所以改到晚上。晚上的湘菜馆还是挺不错的,虽然我感觉不够辣。。吃饭的时候看大家的状态都不错,都挺Happy的。
周六下午热身赛的时候,Dumbear问了我一句:“我们的比赛策略是什么?”
听到这个问题之后,我顿时感觉大脑空白了几秒。
思索了良久这个问题的答案,回想起了以前在ChaeYeon队伍的时候制定的种种策略,比如赛前要制定表格,说好每个人的读题顺序,想好卡题了怎么办,碰到什么类型的题目让谁做,谁负责主要读题,谁负责帮队友debug等等等等。
在回想完这种种之后,不知道为什么感觉这些策略都不值得提出来,于是跟Dumbear说:“到时候看情况,该怎么样就怎么样吧。”
不过虽然没有继续讨论“策略”这个问题,我仍然在那里静静的思考了良久,或许真正的策略便跟武侠里面一样“重剑无锋,无招胜有招”。
这题是我最早想给HDOJ月赛的,现在借lxh达到了这个目的-.-!
http://acm.hdu.edu.cn/vip/contest_showproblem.php?pid=1004&cid=289
没啥特别意思的几何题。。做法就是双重三分,也就是三分里面套一个三分。
放在这里记录一下我的出题成果:) (more…)
Tags: HDOJ Monthly, 三分, 计算几何
在这里简单说一些我出的题目。
F: Pie
此题很明显的是一个最小匹配的模型。而后根据题目数据的特点可以利用DP来解决。
简单说一下性质:
1. 排序之后的匹配是不存在交叉线的。也就是说,如果第i高的男的和第j高的女的匹配,那么比i矮的男的就不能和比j高的女的匹配。同理,比i高的男的也就不能和比j矮的女的匹配。
2. 题目里有说男女的人数相差不到100,所以我们可以知道一个人的的可选匹配数量最多是100个。再加上这个性质,我们就可以用O(n * 100) = O(1000000)的DP来解决这个问题。相关的实现要用到滚存。
附上代码: (more…)
Tags: WHU2010校赛决赛, 动态规划, 匹配, 图论, 计算几何
http://acm.hdu.edu.cn/vip/contest_showproblem.php?pid=1003&cid=276
题目大意就是用下面的距离定义来求每一个点的最近点对。
Distance (A, B) = max (|Ax – Bx|, |Ay – By|)
这题是我最初给我们学校去年的区域赛准备的题目。说一下做法: (more…)
Tags: HDOJ Monthly, 二维线段树, 计算几何
http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1010&cid=288
题意:给定一个简单多边形,问内部哪个点可以看到的面积最大。(可视范围是已知的R,也就是说,如果没有遮挡,那个可视面积就是一个半径为R的圆)
这题是我出的另一个复杂几何,说一下做法: (more…)
Tags: WHU2010校赛预赛, 计算几何
http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1007&cid=288
题目大意:给一个图,求这个图的最大伪森林生成树。所谓的伪森林就是图中的任意两个圈都没有公共部分。 (more…)
Tags: WHU2010校赛预赛, 图论
http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1003&cid=288
题目大意:给一个由两种水果组成的冰糖葫芦,现在需要把这两种水果平均送给两个人。问最少需要切割多少次。(切割不能把水果切开)
这题是我出的,在这里说一下做法: (more…)
Tags: WHU2010校赛预赛
题目地址:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1002&cid=288
题目意思是告诉很多点(最多18个),然后有一些点是固定住了的,有一些点是没有固定的。现在需要用小木棍连接,使得所有的点都固定住。
问小木棍的总长度最短是多少。
这个题目是我出的,在这里说一下这个题目的做法: (more…)
Tags: WHU2010校赛预赛, 动态规划