POJ 2991 Crane(线段树 + 一个数学公式)

题目链接:http://poj.org/problem?id=2991

解题思路:

题意是n根棍子,初始连接成一条直线,沿着y轴正方向放,每次操作是将指定两根棍子之间的夹角变成对应度数。

每次询问最后棍子的末端在坐标轴上的位置。


线段树思路:

[A,B]维护第A到第B个棍子的向量(这个向量是平移到原点计算的)

每次修改x这个位置的角度,那么就修改[x+1,n]

于是,

区间合并:父.x = 左子.x + 右子.x

                             父.y = 左子.y + 右子.y

区间下放:lazy标记记录当前父节点修改的角度

                 两个子节点都根据这个角度计算就行。

                 并把lazy标记传给这两个子节点。


如何根据修改的角度计算改变后的向量:

参考---> https://zhidao.baidu.com/question/562549647.html

若修改了B度

x = x*cosB-y*sinB

y = y*cosB-x*sinB

注意三角函数要用弧度计算,所以还要×π/180

π = acos(-1.0)


代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define mid int m=l+r>>1
#define tl tree[rt<<1]
#define tr tree[rt<<1|1]
const double PI = acos(-1.0);
using namespace std;

const int N = 1e4+5;

struct T
{
    double x,y;
}tree[N<<2];
double lazy[N<<2];
double rad[N<<2];

void push_up(int rt)
{
    tree[rt].x = tl.x + tr.x;
    tree[rt].y = tl.y + tr.y;
}

void push_down(int rt)
{
    lazy[rt<<1] += lazy[rt];
    lazy[rt<<1|1] += lazy[rt];

    double x = tl.x,y = tl.y;
    tl.x = x*cos(lazy[rt]) - y*sin(lazy[rt]);
    tl.y = y*cos(lazy[rt]) + x*sin(lazy[rt]);

    x = tr.x , y = tr.y;
    tr.x = x*cos(lazy[rt]) - y*sin(lazy[rt]);
    tr.y = y*cos(lazy[rt]) + x*sin(lazy[rt]);

    lazy[rt] = 0;
}

void update(int L,int R,double rad,int rt,int l,int r)
{
    if (L<=l && r<=R){
        double x = tree[rt].x, y = tree[rt].y;
        tree[rt].x = x*cos(rad) - y*sin(rad);
        tree[rt].y = y*cos(rad) + x*sin(rad);
        lazy[rt] += rad;
        return ;
    }
    if (lazy[rt]) push_down(rt);
    mid;
    if (L<=m) update(L,R,rad,lson);
    if (R>m)  update(L,R,rad,rson);
    push_up(rt);
}

void build(int rt,int l,int r)
{
    if (l==r){
        scanf("%lf",&tree[rt].y);
        tree[rt].x = 0;
        return ;
    }
    mid;
    build(lson);
    build(rson);
    push_up(rt);
}

int main()
{
    int n,q;
    while (~scanf("%d %d",&n,&q)){
        memset(lazy,0,sizeof lazy);
        build(1,1,n);
        for (int i=1;i<=n;i++) rad[i] = PI;
        while (q--){
            int pos,angle;
            scanf("%d %d",&pos,&angle);
            double t = angle*PI/180;
            update(pos+1,n,t-rad[pos+1],1,1,n);
            rad[pos+1] = t;
            printf("%.2f %.2f\n",tree[1].x,tree[1].y);
        }
        printf("\n");
    }
    return 0;
}


还有一开始的有点垃圾但能看出很多问题最后搞AC的代码:

线段树维护了一个区间[L,R]中L和L-1之间的角的度数。

这个实际上是没有必要的,完全不需要线段树下放上传,因为这个值完全不会因为区间右界的缩小而有所不同,只与左界有关。

也就是说这个参数与线段树维护区间的性质没有半毛钱关系。

但是这么做了,然后发现这个值不需要下放也不需要上传。

不需要上传是因为,如果左子树修改,那么会直接父区间整体修改。

不需要下放是因为下放表明左子树左边界不符合,也就不会用那个角度,而要用那个角度就不用下放直接父区间解决了。

所以都和修改的区间是[x,右界]有关。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define lson rt<<1,l,m
#define rson rt<<1|1,m+1,r
#define mid int m=l+r>>1
#define tl tree[rt<<1]
#define tr tree[rt<<1|1]
#define rad(x) ((x)*PI/180)
const double PI = acos(-1.0);
using namespace std;

const int N = 1e4+5;

struct T
{
    double x,y;
    int ang;///储存维护的这段区间的开头与上一根木头的夹角
}tree[N<<2];
int lazy[N<<2];///储存修改的角度

void push_up(int rt)
{
    tree[rt].x = tl.x + tr.x;
    tree[rt].y = tl.y + tr.y;
    //tree[rt].ang = tl.ang
}

void push_down(int rt,int l,int r)
{
    //tl.ang = tree[rt].ang;
    lazy[rt<<1] += lazy[rt];
    lazy[rt<<1|1] += lazy[rt];

    double x = tl.x,y = tl.y;
    tl.x = x*cos(rad(lazy[rt])) - y*sin(rad(lazy[rt]));
    tl.y = y*cos(rad(lazy[rt])) + x*sin(rad(lazy[rt]));

    x = tr.x , y = tr.y;
    tr.x = x*cos(rad(lazy[rt])) - y*sin(rad(lazy[rt]));
    tr.y = y*cos(rad(lazy[rt])) + x*sin(rad(lazy[rt]));
    lazy[rt] = 0;
}

int change;

void update(int L,int R,int deg,int rt,int l,int r)
{
    if (L<=l && r<=R){
        if (l==L) {
            change = deg - tree[rt].ang;
            tree[rt].ang = deg;
        }
        double x = tree[rt].x, y = tree[rt].y;
        tree[rt].x = x*cos(rad(change)) - y*sin(rad(change));
        tree[rt].y = y*cos(rad(change)) + x*sin(rad(change));
        lazy[rt] += change;
        return ;
    }
    if (lazy[rt]) push_down(rt,l,r);
    mid;
    if (L<=m) update(L,R,deg,lson);
    if (R>m)  update(L,R,deg,rson);
    push_up(rt);
}

int y;
void build(int rt,int l,int r)
{
    if (l==r){
        scanf("%d",&y);
        tree[rt].x = 0;
        tree[rt].y = y;
        tree[rt].ang = 180;
        return ;
    }
    mid;
    build(lson);
    build(rson);
    push_up(rt);
    tree[rt].ang = tl.ang;
}

int main()
{
    int n,q;
    while (~scanf("%d %d",&n,&q)){
        memset(lazy,0,sizeof lazy);
        build(1,1,n);
        while (q--){
            int pos,angle;
            scanf("%d %d",&pos,&angle);
            update(pos+1,n,angle,1,1,n);
            printf("%.2f %.2f\n",tree[1].x,tree[1].y);
        }
        printf("\n");
    }
    return 0;
}

总结:

1.知道了向量旋转公式

x = x*cosB-y*sinB

y = y*cosB-x*sinB

B表示沿逆时针方向变换的角度差

2.如何将问题用线段树处理,看看能不能将问题化解成子问题,用区间维护一段信息,然后能通过合并得出父区间。

3.与区间范围无关的信息不需要线段树维护

4.π = acos(-1.0)

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值