zoukankan      html  css  js  c++  java
  • POJ 2991 Crane (线段树)

    题目链接

    Description

    ACM has bought a new crane (crane -- jeřáb) . The crane consists of n segments of various lengths, connected by flexible joints. The end of the i-th segment is joined to the beginning of the i + 1-th one, for 1 ≤ i < n. The beginning of the first segment is fixed at point with coordinates (0, 0) and its end at point with coordinates (0, w), where w is the length of the first segment. All of the segments lie always in one plane, and the joints allow arbitrary rotation in that plane. After series of unpleasant accidents, it was decided that software that controls the crane must contain a piece of code that constantly checks the position of the end of crane, and stops the crane if a collision should happen.

    Your task is to write a part of this software that determines the position of the end of the n-th segment after each command. The state of the crane is determined by the angles between consecutive segments. Initially, all of the angles are straight, i.e., 180o. The operator issues commands that change the angle in exactly one joint.

    Input

    The input consists of several instances, separated by single empty lines.

    The first line of each instance consists of two integers 1 ≤ n ≤10 000 and c 0 separated by a single space -- the number of segments of the crane and the number of commands. The second line consists of n integers l1,..., ln (1 li 100) separated by single spaces. The length of the i-th segment of the crane is li. The following c lines specify the commands of the operator. Each line describing the command consists of two integers s and a (1 ≤ s < n, 0 ≤ a ≤ 359) separated by a single space -- the order to change the angle between the s-th and the s + 1-th segment to a degrees (the angle is measured counterclockwise from the s-th to the s + 1-th segment).

    Output

    The output for each instance consists of c lines. The i-th of the lines consists of two rational numbers x and y separated by a single space -- the coordinates of the end of the n-th segment after the i-th command, rounded to two digits after the decimal point.

    The outputs for each two consecutive instances must be separated by a single empty line.

    Sample Input

    2 1
    10 5
    1 90

    3 2
    5 5 5
    1 270
    2 90

    Sample Output

    5.00 10.00

    -10.00 5.00
    -5.00 10.00

    题意:
    一开始有n根可任意折叠且首尾相连的木棍,木棍都在y轴上并有自己的长度,有m次操作,每次操作将i和i+1木棍的夹角调成给出的角度,每次调整输出木棍末端的坐标。

    分析:
    首先一个向量(x,y)逆时针绕起点旋转rad度后得到的向量为:

    newx = xcos(rad)-ysin(rad) newy = xsin(rad)+ycos(rad)

    然后我们要知道另外一个事实:如果一条折线由多个向量构成,那么这条折线段的终点坐标一定为起点+向量1+向量2+..+向量n的结果.如下图:

    起点(0,0)经过了4个向量终点必定为(6,2).

    在本题中我们构造一个线段树,该树维护2个信息:

    第一个是本节点(假设控制区间[1,4] )所指代的那几段线段的终点-起点坐标构成的向量x[MAXN4]与y[MAXN4].在上面的图中反应出来就是1号节点维护向量(6,2).虽然1号节点维护的1-4段线段可能构成的是折线,但是我们不管,我们只考虑1号节点的终点和起点整体构成的向量.

    第二个是本节点控制的那几个基本段所需要逆时针旋转的度数dMAXN*4.该信息对本节点没用,但是能通过PushDown操作来更新本节点的左右子节点的向量,使得左右子节点控制的向量逆时针旋转相应的角度.现在有个问题就是,如果整体(A+B两个向量相加后构成的一条直线向量)向量需要逆时针旋转rad度的话且最终旋转后的新向量为VC.此时如果我分别旋转A向量rad度和B向量rad度,然后用旋转后的向量A’和B’相加得到VC’,那么VC是否等于VC’呢?
    这个问题等价于我PushDown
    父节点维护的逆时针旋转度数后,我对左右子节点执行完旋转后,左右子节点表示的向量是否和父节点表示的向量能统一起来.答案是肯定的. 想象下面的图如果整体向量逆转RAD度,折线A+B也逆转RAD度,它们依然构成这个三角形.(此时B是随着A逆转的,不过就算B单独逆转,它形成的新向量与A’相加结果依然相同.因为A与B的相对角度始终不变,这个需要自己画图验证一下).

    1. 首先每个节点的x和y信息都是最新的,d度数只不过是在有需要的时候下放给其儿子,更新儿子的.
      
    2. build(i,l,r)操作: 如果l==r,则读入长度y[i],令x[i]=0,d[i]=0.返回.否则分别建立左右子树,最后执行PushUp操作.
      
    3. PushUp(i)操作: 用儿子们的向量相加赋值给父亲的x和y即可.
      
    4. PushDown(i)操作: 如果d[i]!=0,那么就旋转左右儿子d[i]度,并令
      

    d[i2] +=d[i]; d[i2+1] += d[i];

    1. update(ql,rad,i,l,r)操作: 把第ql块到第n块的段逆时针旋转rad度.如果ql<=l,那么直接旋转当前节点i,并更新d[i]=rad即可.否则先PushDown操作,然后分段判断update,如果m>=ql,那么需要update左边那段,否则只需要update右边这段.最终还要PushUp.
      
    2. rotate(i,deg)操作:将角度转化为弧度,得到旋转后的新坐标,然后更新i节点的x和y坐标.
      
    3. query操作不需要每次只需要查看1节点的向量就是终点坐标.
      

    这里有一点需要注意的,题目中每条指令输入的是i段逆时针旋转d度后到i+1段的位置,但是我们update操作用的是绝对的逆时针旋转度数rad.所以我们需要一个degree[n]数组,其中degree[i]=x表示当前(本次update之前)第i段需要逆转x度才能到i+1段.所以第i+1段到n段实际需要逆转的度数是:d-degree[i]. 自己画图想一想.

    代码:

    #include<iostream>
    #include<stdio.h>
    #include<cmath>
    #define lchild left,mid,root<<1
    #define rchild mid+1,right,root<<1|1
    
    using namespace std;
    const int mm=11111;
    int sd[mm<<2],degree[mm<<2];
    double sx[mm<<2],sy[mm<<2];
    
    void push_up(int root)///通过左右子树,将当前结点的横纵坐标更新
    {
        sx[root]=sx[root<<1]+sx[root<<1|1];
        sy[root]=sy[root<<1]+sy[root<<1|1];
    }
    ///构建线段树
    void build(int left,int right,int root)
    {
        sd[root]=0;///刚开始的时候,所有的线段都是竖直的
        if(left==right)///找到了叶子节点
        {
            scanf("%lf",&sy[root]);
            sx[root]=0;
            return ;
        }
    
        int mid = (left + right)>>1;
        build(lchild);///递归构建左子树
        build(rchild);///递归构建右子树
        push_up(root);///更新根节点的横纵坐标
    }
    
    void rotate(int root,int sd)
    {
        double d=sd*asin(1.0)/90.0;///将输入的角度转换为弧度,然后将这个点的横纵坐标更新
        double x=cos(d)*sx[root]-sin(d)*sy[root];
        double y=sin(d)*sx[root]+cos(d)*sy[root];
        sx[root]=x;
        sy[root]=y;
    }
    
    void push_down(int root)
    {
      rotate(root<<1,sd[root]);
      rotate(root<<1|1,sd[root]);
      ///标记落在下一层
      sd[root<<1]+=sd[root];
      sd[root<<1|1]+=sd[root];
      ///释放本层标记
      sd[root]=0;
    }
    
    void update(int p,int d,int left,int right,int root)
    {
        ///右孩子整棵树的所有的节点肯定是要更新的
        if(p<left)
        {
            rotate(root,d);
            sd[root]+=d;
            return ;
        }
        ///更新角度
        if(sd[root]) push_down(root);
        int mid=(left+right)>>1;
        if(p<mid) update(p,d,lchild);///如果涉及左孩子的区间,就将左孩子的区间更新
        update(p,d,rchild);///右孩子的区间无论如何肯定是要进行更新的
        push_up(root);
    }
    
    int main()
    {
        int i,j,n,m,flag=0;
        while(~scanf("%d%d",&n,&m))
        {
            if(flag)  puts("");///标记第几组数据,控制中间的空行
            else
                flag=1;
            build(1,n,1);///首先建树,分别代表最区间,右区间,以及根节点
            for(i=1;i<n;i++)
                degree[i]=180;///表示最开始的时候的角度
            while(m--)
            {
                scanf("%d%d",&i,&j);///将第i根木棍和第i+1根木棍之间的夹角调整成j度
                update(i,j-degree[i],1,n,1);
                degree[i]=j;
                printf("%.2lf %.2lf
    ",fabs(sx[1])<1e-8?0:sx[1],fabs(sy[1])<1e-8?0:sy[1]);
            }
        }
        return 0;
    }
    
  • 相关阅读:
    RAID卡是否有(启用)缓存对“随机读写”性能有巨大的影响。
    《C++程序设计实践与技巧:测试驱动开发》 环境搭建遇到的坑
    c++ 实现 cout 示例
    c++ 文件
    js 鼠标事件模拟
    eclipse c++ 配置 c++ 17
    c++ 17 vector中string的性能问题 std::vector<std::string> string vs string_view
    c++ 17 模板
    C++17剖析:string在Modern C++中的实现
    编译程序加不加 -lpthread 的区别
  • 原文地址:https://www.cnblogs.com/cmmdc/p/7225573.html
Copyright © 2011-2022 走看看