POJ 2991 Crane(线段树:维护向量+计算几何)

POJ 2991 Crane(线段树:维护向量+计算几何)

http://poj.org/problem?id=2991

题意:

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

分析:

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

newx = x*cos(rad)-y*sin(rad)   newy = x*sin(rad)+y*cos(rad)

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

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

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

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

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


0.     首先每个节点的x和y信息都是最新的,d度数只不过是在有需要的时候下放给其儿子,更新儿子的.

1.     build(i,l,r)操作: 如果l==r,则读入长度y[i],令x[i]=0,d[i]=0.返回.否则分别建立左右子树,最后执行PushUp操作.

2.     PushUp(i)操作: 用儿子们的向量相加赋值给父亲的x和y即可.

3.     PushDown(i)操作: 如果d[i]!=0,那么就旋转左右儿子d[i]度,并令

d[i*2] +=d[i]; d[i*2+1] += d[i];

4.     update(ql,rad,i,l,r)操作: 把第ql块到第n块的段逆时针旋转rad度.如果ql<=l,那么直接旋转当前节点i,并更新d[i]=rad即可.否则先PushDown操作,然后分段判断update,如果m>=ql,那么需要update左边那段,否则只需要update右边这段.最终还要PushUp.

5.     rotate(i,deg)操作:将角度转化为弧度,得到旋转后的新坐标,然后更新i节点的x和y坐标.

6.     query操作不需要每次只需要查看1节点的向量就是终点坐标.

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

AC代码:1016ms.

#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int MAXN = 10000 + 100;
#define lson i*2,l,m
#define rson i*2+1,m+1,r
#define PI acos(-1.0)
int d[MAXN * 4];
double x[MAXN * 4], y[MAXN * 4];
int degree[MAXN];

void rotate(int i, int de)
{
    double rad = PI * de / 180.0;
    double nx = x[i] * cos(rad) - y[i] * sin(rad);
    double ny = x[i] * sin(rad) + y[i] * cos(rad);
    x[i] = nx;
    y[i] = ny;
}
void PushUp(int i)
{
    x[i] = x[i * 2] + x[i * 2 + 1];
    y[i] = y[i * 2] + y[i * 2 + 1];
}
void PushDown(int i)
{
    if(d[i])
    {
        d[i * 2] += d[i];
        d[i * 2 + 1] += d[i];
        rotate(i * 2, d[i]);
        rotate(i * 2 + 1, d[i]);
        d[i] = 0;
    }
}
void build(int i, int l, int r)
{
    d[i] = 0;
    if(l == r)
    {
        x[i] = 0;
        scanf("%lf", &y[i]);
        return ;
    }
    int m = (l + r) / 2;
    build(lson);
    build(rson);
    PushUp(i);
}
void update(int ql, int rad, int i, int l, int r)
{
    if(ql <= l)
    {
        rotate(i, rad);
        d[i] += rad;
        return ;
    }
    PushDown(i);
    int m = (l + r) / 2;
    if(ql <= m) update(ql, rad, lson);
    update(ql, rad, rson);
    PushUp(i);
}
int main()
{
    int n, q, flag = 0;
    while(scanf("%d%d", &n, &q) == 2)
    {
        if(flag == 1)printf("\n");
        flag = 1;
        build(1, 1, n);
        for(int i = 1; i < n; i++) degree[i] = 180;
        while(q--)
        {
            int i, j;
            scanf("%d%d", &i, &j);
            update(i + 1, j - degree[i], 1, 1, n);
            degree[i] = j;
            printf("%.2lf %.2lf\n", fabs(x[1]) < 1e-8 ? 0 : x[1], fabs(y[1]) < 1e-8 ? 0 : y[1]);
        }
    }
    return 0;
}


另外附一份大神代码:

#include<cstdio>
#include<cmath>
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1

///把所有的区间看做等效的一条线段
///旋转的时候认为是只旋转宏观的!中间的细节是不考虑的

using namespace std;
const int mm=11111;
int sd[mm<<2],degree[mm];
double sx[mm<<2],sy[mm<<2];
void rotate(int rt,int sd)
{
    double d=sd*asin(1.0)/90.0;//degrees in rad
    double x=cos(d)*sx[rt]-sin(d)*sy[rt];
    double y=sin(d)*sx[rt]+cos(d)*sy[rt];
    sx[rt]=x,sy[rt]=y;// rotate the sub-tree as a whole~!
}
void pushdown(int rt)//!
{//认为每一条线段都是一个[偏移量], 最终是加和嘛
    rotate(rt<<1,sd[rt]);
    rotate(rt<<1|1,sd[rt]);
    sd[rt<<1]+=sd[rt];//将标记落在下一层
    sd[rt<<1|1]+=sd[rt];
    sd[rt]=0;//清除本层标记
}
void pushup(int rt)
{
    sx[rt]=sx[rt<<1]+sx[rt<<1|1];
    sy[rt]=sy[rt<<1]+sy[rt<<1|1];
}
void build(int l,int r,int rt)
{
    sd[rt]=0;//segment delta degree (must as a whole)
    if(l==r)
    {
        scanf("%lf",&sy[rt]);
        sx[rt]=0;//segment coordinates
        return;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    pushup(rt);//only coordinates
}
void updata(int p,int d,int l,int r,int rt)
{
    if(p<l)//if this sub-tree is completely in the rorated range, rotate.
    {
        rotate(rt,d);
        sd[rt]+=d;
        return;
    }
    if(sd[rt])pushdown(rt);//修正儿子的delta degree
    int m=(l+r)>>1;
    if(p<m)updata(p,d,lson);//如果[涉及]左儿子,就更新
    updata(p,d,rson);///[一定][涉及]右儿子!
    pushup(rt);///再更新总体的坐标
}
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;//degree after ith segment
        while(m--)
        {
            scanf("%d%d",&i,&j);
            updata(i,j-degree[i],1,n,1);//(index, delta degree, tree)
            degree[i]=j;
            printf("%.2lf %.2lf\n",fabs(sx[1])<1e-8?0:sx[1],fabs(sy[1])<1e-8?0:sy[1]);
        }//output root's coordinates, caution: precision
    }
    return 0;
}



 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值