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’相加结果依然相同.因为A与B的相对角度始终不变,这个需要自己画图验证一下).
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; }