挑战程序设计竞赛书本中级篇的一道线段树题目,一开始没有感觉题目和线段树有什么关系。
书上直接给出了题目的翻译:
有一台起重机,我们把起重机看成由N条线段构依次首尾相接而成,第i条线段的长度为Li,最开始,所有线段都笔直朝上。
有C条操作指令,指令i给出两个整数Si和Ai,效果是使线段Si和Si+1之间的角度变成Ai度,其中角度指的是从线段Si开始沿
逆时针方向旋转到Si+1所经过的角度,最开始时所有角度都是180度(当时没好好读题,还傻乎乎地跑到别人博客下问,
为啥角度初始化成了180度,现在看看感觉自己好搞笑)。
按顺序执行C条指令,在每条指令执行之后,输出起重机的前段即第N根线段的端点坐标,假设起重机的支点的坐标为(0,0)。
感觉题目挺难的,写了好久才过了,思路如下:
1.把N条线段看成一个个向量,则最终第N根线段的终点坐标是N个向量的和。如下图所示:
设第i个向量是(xi,yi)则,则第N个线段的终点坐标为 (x1+x2+..+xn,y1+y2+...+yn).
2.将某两条线段之间旋转为ang度,因为原来两条直线之间就存在一个度数deg,因此只用旋转ang-deg度就可以达到既定度数。
如果旋转了Si 和 Si+1之间的角度后,则第Si+1~N这几根线段的角度也会旋转ang-deg度,因为他们是联系在一起的,即对某一
区间的角度都增加ang-deg度,这个时候我们可以用线段树使某个区间一起加上一个定值。则对于一个向量旋转 a = ang-deg度。
有如下数学规律:设一个向量为(X,Y),则旋转a度后的新坐标为( X*cos(a)-Y*sin(a) , X*sin(a)+Y*cos(a) )该公式证明如下。
3.知道上面的内容后,开始用线段树写题。
首先构建线段树。
push_up(int root) 用来更新当前向量的值,由其子向量的和决定。
push_down(int root, int degree) 用来下推标记,当一个向量改变degree度时,下推标记至其孩子。
rolate(root,degree) rolate的中文意思是旋转,这这个函数用来求向量旋转后的新坐标。
update()函数,当改变si 和 si+1 的角度时, 第si+1到第N条线段都要改变相同的度数。
AC代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#define lchild left,mid,root<<1
#define rchild mid+1,right,root<<1|1
using namespace std;
const int maxn = 10010; ///数据规模
double x[maxn<<2]; ///节点横坐标
double y[maxn<<2]; ///纵坐标
int deg[maxn<<2]; ///相连木棍间的角度
int add[maxn<<2]; ///标记某个节点是否需要改变度数的数组
///n根木棍构建线段树,把每个线段看成一个向量
///rolate的意识是旋转,则这是旋转函数,将木棍旋转成需要的度数
void rolate(int root,int degree)
{
double ang = (degree*acos(-1.0))/180; ///化成弧度
double oddx = x[root]; ///旧坐标
double oddy = y[root];
x[root] = oddx*cos(ang) - oddy*sin(ang);
y[root] = oddx*sin(ang) + oddy*cos(ang);
}
void push_up(int root) ///更新当前向量,由子向量得来
{
x[root] = x[root<<1] + x[root<<1|1];
y[root] = y[root<<1] + y[root<<1|1];
}
/**下推标记,当要将s和s+1根木棍调整成一定度数,需要改变角度,
degree度,则s+1后的木棍都是一个整体,也会跟着改变相同的角度**/
void push_down(int root)
{
if(add[root])
{
rolate(root<<1,add[root]); ///旋转左孩子
rolate(root<<1|1,add[root]); ///旋转右孩子
add[root<<1] += add[root]; ///下推标记
add[root<<1|1] += add[root];
add[root] = 0; ///当前节点更新完成
}
}
///n条线段构建线段树。
void build(int left,int right,int root)
{
add[root] = 0;
x[root] = 0;
if(left == right)
{
scanf("%lf",&y[root]); ///纵坐标是向量的长度
return;
}
int mid = (left+right)>>1; ///递归构建左右子树
build(lchild);
build(rchild);
push_up(root);
}
void update(int degree,int L,int R,int left,int right,int root)
{
if(L<=left && right<=R)
{
add[root] += degree;
rolate(root,degree);
return;
}
push_down(root);
int mid = (left+right)>>1;
if(L<=mid) update(degree,L,R,lchild);
if(R>mid) update(degree,L,R,rchild);
push_up(root);
}
int main()
{
int n,c;
int s,ang;
int flag = 0;
while(~scanf("%d%d",&n,&c)) ///输入木棍根数和询问次数
{
if(flag++) ///格式控制。
printf("\n");
build(1,n,1); ///构建线段树。
for(int i = 1; i <= n; i++)
deg[i] = 180;
for(int i = 1; i <= c; i++)
{
scanf("%d%d",&s,&ang);
update(ang-deg[s],s+1,n,1,n,1);
deg[s] = ang;
printf("%.2lf %.2lf\n",x[1],y[1]);
}
}
return 0;
}