POJ2991_Crane_计算几何::向量化|向量旋转公式||线段树维护向量和

Crane
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 6704 Accepted: 1801 Special Judge

Description

ACM has bought a new crane (crane -- jeřáb) . The crane consists of n segments of various lengths, connected by flexible joints. The end of the i-th segment is joined to the beginning of the i + 1-th one, for 1 ≤ i < n. The beginning of the first segment is fixed at point with coordinates (0, 0) and its end at point with coordinates (0, w), where w is the length of the first segment. All of the segments lie always in one plane, and the joints allow arbitrary rotation in that plane. After series of unpleasant accidents, it was decided that software that controls the crane must contain a piece of code that constantly checks the position of the end of crane, and stops the crane if a collision should happen. 

Your task is to write a part of this software that determines the position of the end of the n-th segment after each command. The state of the crane is determined by the angles between consecutive segments. Initially, all of the angles are straight, i.e., 180 o. The operator issues commands that change the angle in exactly one joint. 

Input

The input consists of several instances, separated by single empty lines. 

The first line of each instance consists of two integers 1 ≤ n ≤10 000 and c 0 separated by a single space -- the number of segments of the crane and the number of commands. The second line consists of n integers l1,..., ln (1 li 100) separated by single spaces. The length of the i-th segment of the crane is li. The following c lines specify the commands of the operator. Each line describing the command consists of two integers s and a (1 ≤ s < n, 0 ≤ a ≤ 359) separated by a single space -- the order to change the angle between the s-th and the s + 1-th segment to a degrees (the angle is measured counterclockwise from the s-th to the s + 1-th segment).

Output

The output for each instance consists of c lines. The i-th of the lines consists of two rational numbers x and y separated by a single space -- the coordinates of the end of the n-th segment after the i-th command, rounded to two digits after the decimal point. 

The outputs for each two consecutive instances must be separated by a single empty line.

Sample Input

2 1
10 5
1 90

3 2
5 5 5
1 270
2 90

Sample Output

5.00 10.00

-10.00 5.00
-5.00 10.00

给出 n 条线段,每条线段给定长度,一开始时连接成一条线段。然后给 c 个操作,每次把第 s 条线段与第 s+1 条线段之间的夹角变为 a。每次操作后输出第 n 条线段后面那个点的坐标。


如果单纯地把每一条线段当线段进行旋转操作,那么下面地一条线段发生了旋转,它上面的线段每一条也都要旋转相同的度数,这样以来复杂度为 n*c,恐怕要超时。

注意到,一条线段发生旋转则它上面的线段都要旋转相同的角度,即在原来角度都要加上一个相等的数,这不是一个区间统一加上一个相等的数嘛。由此考虑用线段树来做。

《挑战程序设计竞赛》中给出的做法是:

把每条线段都看成一个向量,这样只需要求出所有向量的和就能得到终点坐标了。说到求和,就用上线段树了。

用线段树维护两个值:

1.(书)把对应的线段集合中的第一条线段转至垂直方向之后,从第一条线段的起点指向最后一条线段的终点的向量。(就是终点对于起点的“相对坐标”,下方的线段转动时只对它本身进行旋转操作,对其上方的线段不进行旋转)

2.两个儿子节点对应的部分链接之后,右子树需要转动的角度(以左子树的终点为轴,右子树需要转动的角度)

这样实际上是把区间修改变成了单点修改。(貌似和上面的分析有些矛盾,看了好多题解才做出来的题,还没有完全理清,有点混。。。)


一个思想:

计算几何中的向量化

两个知识点:

1.保持精度的 pi : 4 * atan1.0

2.向量旋转方程: x' = x * cos - y * sin , y' = x * sin + y * cos 。可以通过矩阵记忆。


#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>

using namespace std;

const int maxn = 10000 + 40;
const double pi = 4 * atan(1.0);		//保证精度的 pi
int n, c;
int L[maxn];							//每条线段的长度
double X[maxn*4], Y[maxn*4], A[maxn*4]; //线段树维护向量、左右子树之间的夹角
int pA[maxn];							//为了求解旋转了多少度而保存当前是多少度

//构建线段树
void Build(int p, int l, int r)
{
	//初始横坐标为0,左右子树之间的夹角为0
	X[p] = A[p] = 0;

	//叶子节点的初始纵坐标等于线段长度
	if(l == r)
	{
		Y[p] = L[l];
		return;
	}

	int m = (l + r) >> 1;

	Build(p<<1,   l, m);
	Build(p<<1|1, m+1, r);

	//非叶子结点的初始纵坐标等于子树长度之和
	Y[p] = Y[p<<1] + Y[p<<1|1];
}

//线段树单点修改更新
//s是操作对象,z是旋转角度
void Change(int p, int l, int r, int s, double z)
{
	//对叶子节点无法操作
	if(l == r) return;

	int chl = p<<1, chr = p<<1|1;
	int mid = (l + r) >> 1;

	if(s <= mid) Change(p<<1, l, mid, s, z);
	else Change(p<<1|1, mid+1, r, s, z);

	//如果左子树中发生了旋转,那么当前角度也要改变
	if(s <= mid) A[p] += z;

	//向量旋转公式
	double si = sin(A[p]), co = cos(A[p]);
	X[p] = X[chl] + X[chr] * co - Y[chr] * si;
	Y[p] = Y[chl] + X[chr] * si + Y[chr] * co;
}

int main()
{
	while(scanf("%d %d", &n, &c) != EOF)
	{
		for(int i= 1; i<= n; i++)
			scanf("%d", L+i);

		//构建线段树
		Build(1, 1, n);
		//当前夹角初始化为180度
		for(int i= 1; i<= n; i++)
			pA[i] = 180;

		for(int i= 1; i<= c; i++)
		{
			int s, a;
			scanf("%d %d", &s, &a);

			//把“变成多少度”转化为“转了多少度”
			//同时把角度值化成弧度值
			double z = (a - pA[s]) / 180.0 * pi;
			pA[s] = a;//更新“当前角度”

			//更新线段树
			Change(1, 1, n, s, z);

			//输出向量和
			printf("%.2f %.2f\n", X[1], Y[1]);
		}

		cout << endl;
	}

	return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值