因为种种原因,学习线段树的事情被耽搁了,直到今天才又开始处理,晚上的时候做掉了这道略微恶心的题,当然也是参考过不少大神的解题报告的
这题题意是,给你n个杆子,连在一起,刚开始在y轴上,每次变化第s和s+1杆子之间的夹角(当然后面的所有杆子都会跟真转动),然后输出第n条杆子的那个端点的坐标……
因为后面的杆子是跟着动的,所以他们整体上的变化的角度都是一样的,这就变成了区间更新的线段树了,查询很简单,就是顶点的坐标
线段树节点保存该区间(即第i到第j条杆子的整体,从起点到终点)的一个向量,存了x和y坐标(这是向量,不是实际在坐标系中的坐标!)
add数组就是懒惰标记。
因为一个向量(x,y)逆时针转角度a以后,向量变成(cos(a)*x-sin(a)*y,sin(a)*x+cos(a)*y),所以,我们在add中记录当前区间逆时针转的角度
用一个angle数组存相邻线段之间的角度,通过这个值和到时候要修改成的角度a来换算出逆时针转过的角度
push_up函数就是把两个子区间的向量相加,push_down就是如果要更新一个区间的子区间,那么如果这个区间的add不为0(说明这个懒惰标记还没传递下去),则需要将信息传递下去,即左右区间旋转add角度,同时左右区间的add值要增加
其他就是注意精度问题,为了防止输出-0.00 输出时加一个esp
#include <cstdio>
#include <cstring>
#include <cmath>
#define PI acos(-1.0)
#define eps 1e-9
const int MAX = 10010;
struct point
{
double x,y;
}p[MAX<<2];
int add[MAX<<2];
int angle[MAX];
void push_up(int rt)
{
p[rt].x = p[rt<<1].x+p[rt<<1|1].x;
p[rt].y = p[rt<<1].y+p[rt<<1|1].y;
}
void rolate(int a,int rt)
{
double x = p[rt].x;
double y = p[rt].y;
double ang = PI/180*a;
p[rt].x = cos(ang)*x-sin(ang)*y;
p[rt].y = sin(ang)*x+cos(ang)*y;
}
void push_down(int rt)
{
if(add[rt])
{
rolate(add[rt],rt<<1);
rolate(add[rt],rt<<1|1);
add[rt<<1]+=add[rt];
add[rt<<1|1]+=add[rt];
add[rt] = 0;
}
}
void build(int l,int r,int rt)
{
if(l==r)
{
scanf("%lf",&p[rt].y);
p[rt].x = 0;
return;
}
int mid = l+r>>1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
push_up(rt);
}
void update(int a,int L,int R,int l,int r,int rt)
{
if(L<=l&&R>=r)
{
add[rt]+=a;
rolate(a,rt);
return;
}
push_down(rt);
int mid = r+l>>1;
if(L<=mid) update(a,L,R,l,mid,rt<<1);
if(R>mid) update(a,L,R,mid+1,r,rt<<1|1);
push_up(rt);
}
int main()
{
int n,m;
int cnt = 0;
while(scanf("%d%d",&n,&m)==2)
{
if(cnt++) puts("");
build(1,n,1);
for(int i=1; i<=n; i++)
angle[i] = 180;
memset(add,0,sizeof(add));
while(m--)
{
int s,a;
scanf("%d%d",&s,&a);
int x = (a-angle[s]+360)%360;
update(x,s+1,n,1,n,1);
angle[s]=a;
printf("%.2lf %.2lf\n",p[1].x+eps,p[1].y+eps);
}
}
return 0;
}