POJ 2991 线段树+计算几何
(2011-02-27 21:13:44)
标签:
杂谈 |
分类: OI |
话说这一题真的是很恶心很恶心,不过确实改变了我对线段树的一些看法,算是很经典的题目。
题意:有一个吊车由很多个不同长度的线段组成,一开始是一条长直线起点在(0,0),尾节点在(0,sum[n]),每条线段之间的夹角的初始值是180度。然后有一些操作a、 b将第a条线段和a+1之间的夹角变成b度,经过每一次操作都要求出尾节点的坐标。
首先要用到一个计算几何的知识(没学过。。请教而来):
对于一个向量x,y,逆时针旋转a度得到的新向量x‘、y‘,可以由如下推导
[x y]*[cos(a) sin(a)] = [x‘ y‘]
[-sin(a) cos(a)]
然后再是一个很简单的结论,一个线段旋转后,后面的线段相对之间的角度没有发生变化,从这一点我觉得很容易的就可以和线段树的标记下传结合起来。
线段树记录线段的起始和结束节点,以及标记下传的改变角度。
对于一条线段的修改是这样操作的:
如果该线段的起点正好等于一线段树中一个节点的起点:将该向量以首节点直接旋转,并将这个角度计入标记下传中的角度,标记这条线段。
否则将这条线段存在于左子树的部分旋转,然后修改右子树的两端点然后继续旋转右子树。
最后将目前节点右子树的末坐标上传。
我觉得本题的精华之处就在于左子树和右子树两者信息的联合,左子树修改后影响了右子树,这样的题目还是第一次遇到。
代码写的不好,借鉴了别人的一些。
#include <cstdio>
#include <cmath>
#define PI acos(-1.0)
#define eps 1e-8
#define L(x) (x << 1)
#define R(x) ((x << 1) | 1)
#define maxn 10001
int N, Q;
int len[maxn], sum[maxn];
double mySin[720], myCos[720];
struct Point
{
double x,
y;
Point
operator +(const Point & a)const
{
Point ret;
ret.x = x + a.x;
ret.y = y + a.y;
return ret;
}
Point
operator -(const Point & a)const
{
Point ret;
ret.x = x - a.x;
ret.y = y - a.y;
return ret;
}
};
struct Segtree
{
int l, r,
deg;
Point s,
e;
bool
mark;
int
mid()
{
return (l + r) >> 1;
}
void
Rotate(int angle)
{
double dx = e.x - s.x, dy = e.y - s.y;
double newx = dx * myCos[angle + 360] - dy * mySin[angle +
360];
double newy = dx * mySin[angle + 360] + dy * myCos[angle +
360];
e.x = s.x + newx;
e.y = s.y + newy;
}
} tree[maxn * 4];
void Build(int rt, int l, int r)
{
tree[rt].l =
l, tree[rt].r = r;
tree[rt].deg
= 0;
tree[rt].mark = 0;
tree[rt].s.x
= tree[rt].e.x = 0;
tree[rt].s.y
= sum[l] - len[l];
tree[rt].e.y
= sum[r];
if (l !=
r)
{
Build(L(rt), l, tree[rt].mid());
Build(R(rt), tree[rt].mid() + 1, r);
}
}
void Update(int rt)
{
int l =
L(rt), r = R(rt);
//提出向量
根据新节点进行旋转
tree[l].deg
+= tree[rt].deg;
tree[l].deg
%= 360;
tree[l].e =
tree[l].e - tree[l].s + tree[rt].s;
tree[l].s =
tree[rt].s;
tree[l].Rotate(tree[rt].deg);
tree[r].e
= tree[r].e - tree[r].s + tree[l].e;
tree[r].s =
tree[l].e;
tree[r].deg
+= tree[rt].deg;
tree[r].deg
%= 360;
tree[r].Rotate(tree[rt].deg);
tree[l].mark = tree[r].mark = 1;
tree[rt].mark = 0;
tree[rt].deg
= 0;
}
int Query(int rt, int order)
{
if
(tree[rt].l == tree[rt].r) return tree[rt].deg;
//只用叶节点记录真实的角度
if
(tree[rt].mark) Update(rt);
int LL =
L(rt), RR = R(rt);
if (order
<= tree[LL].r) return Query(LL, order);
else return
Query(RR, order);
}
void Change(int rt, int l, int angle)
{
if
(tree[rt].l != tree[rt].r &&
tree[rt].mark) Update(rt);
if
(tree[rt].l == l)
{
tree[rt].deg += angle;
tree[rt].deg %= 360;
tree[rt].Rotate(angle);
tree[rt].mark = 1;
return;
}
int LL =
L(rt), RR = R(rt);
int mid =
(tree[rt].l + tree[rt].r) >> 1;
if (l
<= mid)
{
Change(LL, l, angle);
tree[RR].e = tree[RR].e + tree[LL].e - tree[RR].s;
tree[RR].s = tree[LL].e;
Change(RR, mid + 1, angle);
}
else
Change(RR, l, angle);
tree[rt].e =
tree[RR].e;
}
int main()
{
int cases =
0;
for (int i =
-360; i <= 360; ++i)
{
mySin[i + 360] = sin(i * PI / 180);
myCos[i + 360] = cos(i * PI / 180);
}
while
(~scanf("%d%d", &N, &Q))
{
if (cases++) puts("");
sum[0] = 0;
for (int i = 1; i <= N; ++i)
{
scanf("%d", len + i);
sum[i] = sum[i - 1] + len[i];
}
Build(1, 1, N);
for (int i = 1; i <= Q; ++i)
{
int a, b;
scanf("%d%d", &a, &b);
b = ((Query(1, a) - Query(1, a + 1) + 180 + b) % 360 + 360) %
360;
Change(1, a + 1, b);
printf("%.2lf %.2lf\n", tree[1].e.x + eps, tree[1].e.y +
eps);
}
}
return
0;
}