线段树的应用 poj2991

题意:有一个挖掘机的手臂,它有很多节,开始的时候都是垂直于地面的,然后要对手臂的关节部分进行旋转,旋转到:从逆时针开始的角度a。每一次旋转后,都需要输出最后一节的尾部的坐标。

 

每次旋转的时候,需要用表达式来来表达,这个表达式选择的是向量,向量的旋转还要表达出来。没有学好向量,看到这题很懵逼。向量的旋转和复数的运算有关。

两个复数:z1=x1+y1*i=r1*(cosa1+sina1*i),z2=x2+y2*i=r2*(cosa2+sina2*i)

z1*z2=r1*r2(cosa1*cosa2+cosa1*sina2*i+sina1*i*cosa2-sina1*sina2)

=r1*r2(cosa1*cosa2-sina1*sina2+(cosa1*sina2+sina1*cosa2)*i)

//三角变换,cos(a+b)=cosa*cosb-sina*sinb,sin(a+b)=sina*cosb+cosa*sinb

=r1*r2(cos(a1+a2)+sin(a1+a2)*i)

由此可以看出两个复数相乘等于模相乘,角度相加。

所以对于一个向量z=(x,y)=x+y*i,把它和一个模为1的向量cosa+sina*i相乘

就相当于把它加上了角度a,也就是逆时针旋转了a度

z'=x*cosa+x*sina*i+y*i*cosa-y*sina

=x*cosa-y*sina+(x*sina+y*cosa)*i

z'=(x*cosa-y*sina,x*sina+y*cosa)

即x'=x*cosa-y*sina,y'=x*sina+y*cosa;

然后还需要一个prv数组来保存原来的角度,以便算出旋转了多少。

之后的线段树操作就没有什么特别了。

值得注意的是:if (s <= mid)ang[k] += a;,只有当s<=mid的时候角度才能加上a,因为是先修改的左儿子和右儿子的值,

当s>mid的时候已经改变了右儿子的值,而这个题是修改s和s+1之间的角度,如果s在mid右边,改变了右边的值就不需要再修改了,修改右边的值对左边的产生不了影响。

然而修改左边的值,只是改变了左边的值,但是实际上右边的值依然需要修改,因为要求的是最右边的端点的坐标。

#pragma warning(disable:4996)
#include <cstring>
#include<cstdio>
#include <algorithm>
#include<iostream>
#include<cmath>
#include<queue>
#include<set>
#include<vector>
#include<stack>
#include<climits>
using namespace std;
typedef long long ll;
const int maxn = 20005;
//数组开大点,不然RE
double vx[maxn<<2], vy[maxn<<2];
const double PI =acos(-1.0);
double prv[maxn<<2], ang[maxn<<2];
int A[maxn<<2], S[maxn<<2];
int L[maxn<<2];
//建树,初始化每个结点坐标的x和y值
void init(int k, int l, int r)
{
	ang[k] = 0;vx[k] = 0;
	if (r == l)vy[k] = L[l];
	else
	{
		int mid = (l + r) >> 1;
		init(2 * k, l, mid);
		init(2 * k + 1, mid + 1, r);
		vy[k] = vy[2 * k] + vy[2 * k + 1];
	}
}
void change(int s, double a, int k, int l, int r)
{
	if (s<l || s>=r)return;
	int mid = (l + r) / 2;
	change(s, a, 2 * k, l, mid);
	change(s, a, 2 * k + 1, mid+1, r);
	if (s <= mid)ang[k] += a;//一定要判断
	double ss = sin(ang[k]), cc = cos(ang[k]);
	//左边的加上右边旋转后的
	vx[k] = vx[2 * k ] + (vx[2 * k +1] * cc - vy[2 * k + 1] * ss);
	vy[k] = vy[2 * k ] + (vx[2 * k +1] * ss + vy[2 * k + 1] * cc);
}
int main()
{
	int N, C, i, j, cnt = 0;
	while (scanf("%d%d", &N, &C) == 2)
	{
		if (cnt != 0)
		{
			printf("\n");
		}
		cnt++;
		for (i = 1;i <= N;i++)
			scanf("%d", &L[i]);
		for (i = 1;i <= C;i++)
		{
			scanf("%d%d", &S[i], &A[i]);
		}
		init(1,1,N);
		for (i = 1;i < N;i++)
		{
			prv[i] = PI;//原来都是竖直的,角度为180度
		}
		for (i = 1;i <= C;i++)
		{
			int s = S[i];
			double a = A[i] / 180.0 * PI;//转换为弧度制
			change(s, a - prv[s], 1, 1, N);
			prv[s] = a;
			printf("%.2lf %.2lf\n", vx[1], vy[1]);
		}
	}
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值