POJ 2991:Crane

点击打开题目链接

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

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

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






  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值