Picture
这个题目需要运用线段树+离散化+扫描线
这个算法的理解难度挺大,我独自看别人的解法还有解释看了两天 终于理解了解题的思路。
下面给出我的代码还有思路。
这个非常奇怪的算法似乎想要用数学方法证明的话难度是很大的,但是确实具有这个规律的。
首先解释离散化的必要性,其实在我看来这个题目其实不离散化也是可以做的,不过由于坐标出现负值,不符合一般的建树模式,并且可能因为坐标的不连续出现大量的空子树导致空间和时间的很大浪费。所以需要离散化。
此题的建树节点的左右信息采用的是y坐标,因此要单独开一个y[N]的区间来存储y坐标,并排序,去除重叠点去顶离散化后的节点数量。另外由于需要每条边都要映射到y轴上,左右要将每条边的两个y坐标存储起来,依据x坐标进行排序,还有就是是矩形的左边的话就insert更新然后计算 是右边的话就remove更新。所以在存储边的结构体当中加入flag用于表示左右边,左边为1,右边为0;
用于存储矩形左右边的结构体
struct Node
{
int left; //区间左端点
int right; //区间右端点
int line;
int time; //此边被重叠次数
int line; //用于计算现在投影的段数 来求横边
int lbd; //用于判断该区间的左端点是否被投影覆盖
int rbd; //用于判断该区间的右端点是否被投影覆盖
int len; //用于求出该区间内要计算为投影长度的长度
};
每次计算时用根节点当前的m值减去上一次投影时的m值 得到结果的绝对值就是 计算在这个线段(就是扫描线)加入后的周长竖线增加量
每次计算时用根节点上一次的line值*2*(这一次添加线段的横坐标 -上一次添加线段的横坐标) 用于周长横线增加量
具体为什么要这样做 很难说清楚 似乎是一个规律 找个例子实践下还真是对的
还有 就是这个题目在建树的时候叶子节点是[i,i+1]形式的 因为此题叶子节点对应的是一段线段而不是一个点的数据
所以左右子树变成了 i*2+1 和i*2+2
详细见注释 此代码是参照别人的代码修改而来 但是自己所写 进行了优化删改并亲自敲出
时间复杂度:线段树的修改操作和查询操作都是logN,一共执行Q次修改和查询操作 故时间复杂度为O(QlogN)当然这一题是修改更新了两次的 update_len update_line
空间复杂度:线段树用堆实现 故空间复杂度为O(3N) 有种说法是 3N属于正常,4N属于保守,2N属于拼人品
本题也是用的4N的
#include<cstdio>
#include<algorithm>
#define N 10005
#define MID(x,y) ((x+y)>>1)
#define L(x) (x<<1|1)
#define R(x) ((x<<1)+2)
using namespace std;
struct node //用于建树用的节点
{
int left;
int right;
int line; //所包含的区间块数-
int time; //覆盖次数
int lp;
int rp; // 用于记录区间左右端点是否被覆盖
int len; //用于记录被投影的区间长度
};
struct rectLine //用于存储矩形左右边用于扫描
{
int flag; // 用于标记左右边 左边记为1 右边记为 0
int x;
int y1; //y1<y2
int y2;
};
rectLine scan[N]; //用于扫描
node tree[N*4];
int y[N];
int a,x1,x2,y1,y2;
void createTree(int left,int right,int id)
{
tree[id].left = left;
tree[id].right = right;
tree[id].time =0;
tree[id].len =0;
tree[id].line = 0;
tree[id].lp = 0;
tree[id].rp = 0;
int mid = MID(left,right);
if(right - left >1)
{
createTree(left,mid,L(id));
createTree(mid,right,R(id));
}
}
void update_len(int id)
{
if(tree[id].time>0)
tree[id].len = y[tree[id].right] - y[tree[id].left];
else
{
tree[id].len = (tree[L(id)].len + tree[R(id)].len);
}
}
void update_line(int id) //就是这样的函数之间也是有顺序的,如果
{
if(tree[id].time>0)
{
tree[id].lp = 1;
tree[id].rp = 1;
tree[id].line = 1;
}
else
{
tree[id].lp = tree[L(id)].lp; //这其实是一个回溯的过程 在insert和remove里面的
tree[id].rp = tree[R(id)].rp;
tree[id].line = tree[L(id)].line + tree[R(id)].line -(tree[L(id)].rp * tree[R(id)].lp);
}
}
void insert(int left,int right, int id)
{
if(y[tree[id].left]>=left&&y[tree[id].right]<=right) // 要与离散化前的坐标值对比
(tree[id].time)++; //重叠次数加1
else
{
int mid = MID(tree[id].left,tree[id].right);
if(right<=y[mid])
insert(left,right,L(id));
else if(left>=y[mid])
insert(left,right,R(id));
else
{
insert(left,y[mid],L(id));
insert(y[mid],right,R(id));
}
}
update_len(id);
update_line(id);
}
void remove(int left,int right,int id)
{
if(y[tree[id].left]>=left&&y[tree[id].right]<=right)
(tree[id].time)--;
else
{
int mid = MID(tree[id].left,tree[id].right);
if(right<=y[mid])
remove(left,right,L(id));
else if(left>=y[mid]) //注意这是在跟离散后的原值进行比较
remove(left,right,R(id));
else
{
remove(left,y[mid],L(id));
remove(y[mid],right,R(id));
}
}
update_len(id);
update_line(id);
}
bool cmp(rectLine x,rectLine y)
{
if(x.x==y.x) return x.flag>y.flag;
else
return x.x<y.x;
}
int main()
{ //freopen("1.txt","r",stdin);
while(scanf("%d",&a)==1)
{
int i = 0;
while(a--)
{
scanf("%d %d %d %d",&x1,&y1,&x2,&y2);//我日 这里竟然出现错误
scan[i].x = x1;
scan[i].y1 = y1;
scan[i].y2 = y2;
scan[i].flag =1;
y[i++] = y1;
scan[i].x = x2;
scan[i].y1 = y1;
scan[i].y2 = y2;
scan[i].flag =0;
y[i++] =y2;
}
sort(y,y+i); //对y坐标进行排序
sort(scan,scan+i,cmp);
int count = unique(y,y+i)-y; // unique函数只是将相同数组元素放到后面并没有实际删除这些元素
createTree(0,count-1,0); //此时就是进行了离散化 将y坐标进行了离散化
int result = 0;
int last_line = 0;
int last_len = 0;
for(int j=0;j<i;j++)
{
if(scan[j].flag)
insert(scan[j].y1,scan[j].y2,0);
else
remove(scan[j].y1,scan[j].y2,0);
if(j>0) //第0条边不需要进行横边的计算 只需要进行竖边的计算
result += 2*last_line*(scan[j].x - scan[j-1].x); //加入横边
result +=abs(tree[0].len - last_len); //计算竖边
last_len = tree[0].len;
last_line = tree[0].line;
}
printf("%d\n",result);
}
return 0;
}