我有一颗线段树,我有一组扫描线,线段树,扫描线,线段树扫描线,扫描线线段树。
--盗版-PPAP-OTZ发明这个的大佬
接下来一段是蒟蒻的思考,请学扫描线的童鞋自觉跳过,不过如果没人更早题出这种算法,请允许我保留命名权(一脸滑稽)
在学开始扫描线的时候。我觉得扫描线完全可以用前向星去更新,head[u]->line[1]->line[2]->...插入应该线段有两种情况从line【1】开始扫,碰到有交集的就和起来,并去掉原line,最后线段树访问所有根节点的前向星《树式前向星》,感觉自己有了什么大发现。p.s.这样应该是可以过这个题(p o j-1177)的。面积更新也是可以的,加lazy标记,储存一个前向星,然后有必要时向下传递也是可以的,如果没有扫描线算法,我多半是个可以写个论文发表了。《空间复杂度高了个常数,速度上应该也差不多》
进入正题
扫描线求这种周长其实有两种策略
一种是只用一颗线段树的
扫描线是要存在的,所以我们用一个类Line储存,每个Line储存他的横坐标,两个端点的纵坐标,和矩形出入口属性。因为他要排序重载<操作符或写一个继承一个Comparable,使它具有排序能力。这是为了保证每一个后插入的边都有在下一个边的右边(不是严格的右,包括它本身的),这样需要去重的部分就只有x相同的地方了。x不同的时候就是线段树的区间,求区间长度有没有覆盖过(这里的numseg表示覆盖过的个数),对每个区间更新一个flag值《+1,-1代表加一个或减一个边》即可,只要不是1就可以加上去,所以可以构造出。然后横向的边就是这个线和上一个线的x的差值。
线段树各个非叶子节点是长度的和,叶子节点是这个点进入的次数
各种值的定义后面都有说明,这里不兹论述,请见代码。
离散化不是这里的重点,简单概述一下,会的跳过。离散化实际上应该是连续化,是把离散的点边的连续的方法。可以大大减少线段树的区间总长度,减少占地空间,加快运算。而通过离散的值《在数组里就是下标》又很容易和原值对应《下标和值对应天经地义好吧》,代码里面有个去重函数,排序加去重全用c++标准程式库就好了。就这了。
通过插入两条边时的情况让大家理解线段树的作用,显然,x2-x1下面line2上的线段被重复计算了,而题意要去去掉额外的轮廓线,所以我们要想办法去掉它,线段树的价值
这时候线段树救发挥功效了
不妨假设line1的上下端点为2,0,line2的上下端点为1,3
//代码里的y数组应该叫x,这里忽略不计谢谢
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 10010;
class Tree {
//纵向扫描线
private:
class Line {
public:
int x;
int y1;//顶点
int y2;//底点
int flag;//矩形左侧边表示进入矩形,标记为1,出矩形为-1
bool operator < (const Line t) const {
return x < t.x;
}
void set(int x, int y1, int y2, int f){
this->x = x;
this->y1 = y1;
this->y2 = y2;
this->flag = f;
}
Line() {}
};
class Node {
public:
int l, r;
int cnt;//有效长度
int lf, rf;//实际的左右端点
int numseg;//分支数,一个分支对应两条竖线
int c;//记录覆盖情况
bool lcover, rcover;
void set(int l, int r, int lf, int rf) {
this->l = l;
this->r = r;
this->rf = rf;
this->lf = lf;
this->cnt = 0;
this->c = 0;
this->numseg = 0;
this->c = 0;
this->lcover = this->rcover = false;
}
Node() {}
};
Node segTree[MAXN << 2];
Line line[MAXN];
int y[MAXN];
void build(int i, int l, int r) {
segTree[i].set(l, r, y[l], y[r]);
if (l + 1 == r)return;
int mid = (l + r) >> 1;
build(i << 1, l, mid);
build((i << 1) | 1, mid, r);
}
void calen(int i){
if (segTree[i].c>0){
segTree[i].cnt = segTree[i].rf - segTree[i].lf;
segTree[i].numseg = 1;
segTree[i].lcover = segTree[i].rcover = true;
return;
}
if (segTree[i].l + 1 == segTree[i].r){
segTree[i].cnt = 0;
segTree[i].numseg = 0;
segTree[i].lcover = segTree[i].rcover = false;
}
else{
segTree[i].cnt = segTree[i << 1].cnt + segTree[(i << 1) | 1].cnt;
segTree[i].lcover = segTree[i << 1].lcover;
segTree[i].rcover = segTree[(i << 1) | 1].rcover;
segTree[i].numseg = segTree[i << 1].numseg + segTree[(i << 1) | 1].numseg;
if (segTree[i << 1].rcover&&segTree[(i << 1) | 1].lcover)segTree[i].numseg--;
}
}
void update(int i, Line e) {
if (segTree[i].lf == e.y1&&segTree[i].rf == e.y2) {
segTree[i].c += e.flag;
calen(i);
return;
}
if (e.y2 <= segTree[i << 1].rf)
update(i << 1, e);
else if (e.y1 >= segTree[(i << 1) | 1].lf)
update(i << 1 | 1, e);
else {
Line temp = e;
temp.y2 = segTree[i << 1].rf;
update(i << 1, temp);
temp = e;
temp.y1 = segTree[i << 1 | 1].lf;
update(i << 1 | 1, temp);
}
calen(i);
}
int work() {
int ans = 0;
int last = 0;
for (int i = 0; i<t - 1; i++){
update(1, line[i]);
ans += segTree[1].numseg * 2 * (line[i + 1].x
- line[i].x);
ans += abs(segTree[1].cnt - last);
last = segTree[1].cnt;
}
update(1, line[t - 1]);
ans += abs(segTree[1].cnt - last);
return ans;
}
int t;
public:
Tree() {}
void build(int n) {
int x1, x2, y1, y2;
t = 0;
for (int i = 0; i < n; i++) {
cin >> x1 >> y1 >> x2 >> y2;
line[t].set(y1, x1, x2, 1);
y[t++] = x1;
line[t].set(y2, x1, x2, -1);
y[t++] = x2;
}
sort(line, line + t);
sort(y, y + t);
int m = unique(y, y + t) - y;
build(1, 0, m - 1);
}
void printAns() {
cout << work() << endl;
}
~Tree() {
free(segTree);
free(y);
free(line);
}
};
Tree *tree;
int main() {
int n;
while (cin >> n) {
tree = new Tree();
tree->build(n);
tree->printAns();
free(tree);
}
}
/*
*都是腰间盘,你为何如此突出
*It's all the waist disc, why are you so protruding
*/
一种是横竖各一个线段树,分别储存纵边和横边。这种比较简单,就讲下思路,线段树上确定的子节点上,给这个区间上加边的数量和长度,每次加入一个新的边然后把重的地方重算,就好了。