POJ - 2991 Crane(线段树区间更新+几何)

题目链接

POJ - 2991

题目大意

有一个吊车臂,有相连的 n 段,每两段相连的地方可以旋转。初始吊车臂是竖直的,给出n段的长度。之后有 C 个·操作:
sθ ,表示将第 s 段与第s+1段的角度旋转为 θ 度。(题目上说这里的角度是相对逆时针而言的,似乎顺逆不大影响)
在每个操作后,输出最顶端那个点的坐标。

数据范围

1n10000C 没看到
1li1001s<n0θ359

解题思路

一般像这种求坐标的题,为了保证精度都会用向量,这道题也是的。
先前我只知道在直角坐标系中,从原点开始的向量和坐标是一一对应的关系,在复平面内,一个从原点开始的向量和复数是一一对应的关系。做了这道题之后,才知道坐标、向量、复数 这三者都存在一一对应的关系。
即点 Z(a,b) ,复数 z=a+bi ,向量 OZ 存在下图的对应关系:
这里写图片描述


回到正题,将每一段用向量表示(由起点到终点),最后顶点的坐标就为所有小段的向量和。(这个想想都应该知道,首尾相接)
对于这道题而言,某一操作” sθ “,旋转之后, s 后面的那些线段的相对关系是不会变的,其实就相当于后面的所有线段都旋转了θ(这个感觉就是对的,就不多说了)。这就和线段树联系上了,对于每次旋转,将区间 [s+1,n] lazy+=θ 就可以了。
最终是要得到坐标,所以要通过旋转的角度来求出旋转后的坐标。
一个复数有多种表示形式,如: z=a+bi=Reiφ=R(cosφ+isinφ) (其中 R 表示复数z的模长)
欧拉公式: eiφ=cosφ+isinφ
如下图,复平面上一个向量 OZ=(x,y) ,其中 x=Rcosφy=Rsinφ
这里写图片描述
逆时针旋转 θ 之后, OZ=Rei(φ+θ)=R(cos(φ+θ)+isin(φ+θ)) ,好!来!公式推起!
OZ=R(cosφcosθsinφsinθ+i(sinφcosθ+cosφsinθ))
OZ=RcosφcosθRsinφsinθ+i(Rsinφcosθ+Rcosφsinθ)
其中 x=Rcosφy=Rsinφ
OZ=xcosθysinθ+i(ycosθ+xsinθ)

所以, x=xcosθysinθy=ycosθ+xsinθ
顺时针的话就把 OZ=Rei(φθ)

公式推出来了,接下来就交给线段树了,详见代码:

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <algorithm>
#include <set>
#include <map>
#include <queue>
using namespace std;
typedef long long LL;
const int inf = 1 << 30;
const LL INF = 1LL << 60;
const int MaxN = 10010;
const double PI = 3.1415926535897932384626;

int n, C;
struct segtree {
    int l, r;
    double x, y;    //当前节点所管辖区间的向量
}tree[4 * MaxN + 5];
int lazy[4 * MaxN + 5]; //当前节点所管辖区间 要旋转的角度
int angle[MaxN + 5];    //angle[i]表示节点i和(i + 1)当前的夹角,依照题意,逆时针
double Len[MaxN + 5];   //各线段长度

//其实就是区间修改,只是为了贴合题意,取名叫rotate
void rotate(int rt, int val) {
    double Theta = (double)val * PI / 180.0; //转化为弧度制

    //向量旋转Theta弧度后,求新坐标的公式
    double x = tree[rt].x * cos(Theta) - tree[rt].y * sin(Theta);   
    double y = tree[rt].x * sin(Theta) + tree[rt].y * cos(Theta);
    tree[rt].x = x;
    tree[rt].y = y;
}

void push_up(int rt) {
    tree[rt].x = tree[rt << 1].x + tree[rt << 1 | 1].x;
    tree[rt].y = tree[rt << 1].y + tree[rt << 1 | 1].y;
}

void push_down(int rt) {
    if(lazy[rt] != 0) { //保存的是旋转的角度,所以有可能是负的!!!挂了不知多少发!!!
        lazy[rt << 1] += lazy[rt];
        lazy[rt << 1 | 1] += lazy[rt];

        rotate(rt << 1, lazy[rt]);
        rotate(rt << 1 | 1, lazy[rt]);
        lazy[rt] = 0;
    }
}

void Build(int rt, int l, int r) { //建树
    tree[rt].l = l; tree[rt].r = r;
    lazy[rt] = 0;
    if(l == r) {
        tree[rt].x = 0.0;
        tree[rt].y = Len[l];
        return;
    }
    int mid = (l + r) >> 1;
    Build(rt << 1, l, mid);
    Build(rt << 1 | 1, mid + 1, r);
    push_up(rt);
}

void update(int rt, int L, int R, int val) {
    if(L <= tree[rt].l && tree[rt].r <= R) {
        lazy[rt] += val;
        rotate(rt, val);
        return;
    }
    push_down(rt);
    int mid = (tree[rt].l + tree[rt].r) >> 1;
    if(L <= mid) update(rt << 1, L, R, val);
    if(R > mid) update(rt << 1 | 1, L, R, val);
    push_up(rt);
}

int main() 
{
    while(scanf("%d %d", &n, &C) != EOF)
    {
        for(int i = 1; i <= n; i++) scanf("%lf", &Len[i]);
        Build(1, 1, n);
        for(int i = 1; i <= n; i++) angle[i] = 180; //初始为180

        for(int i = 1; i <= C; i++) {
            int a, b;
            scanf("%d %d", &a, &b);
            update(1, a + 1, n, b - angle[a]);
            angle[a] = b;
            printf("%.2lf %.2lf\n", tree[1].x, tree[1].y);
            //最后答案显然就是根节点的向量
        }
        printf("\n");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值