题目链接: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)