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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章