POJ 2991:Crane

来源:互联网 发布:淘宝p图教程 编辑:程序博客网 时间:2024/05/19 01:58

点击打开题目链接

挑战程序设计竞赛书本中级篇的一道线段树题目,一开始没有感觉题目和线段树有什么关系。

书上直接给出了题目的翻译:

有一台起重机,我们把起重机看成由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|1using 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;}