挑战程序设计竞赛3.3例题:Crane POJ - 2991
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.
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).
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.
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
这是一道比较搞的线段树+计算几何题,结合白书简单归纳下本题:
1.这道题因为每次修改一个值以后从第n块的坐标都会发生变化,所以如果2e5的数据量去模拟当场超时,而用logn的模拟方法就是线段树。
2.所以说得用线段树,算坐标很麻烦,不如直接计算两段向量的和直接算出坐标,第一段向量A的坐标从第一块指向到n/2块,第二段向量B的坐标从n/2到n + 1(都是起始块的头指向末尾块的头,例如n = 2,第一块1头->2头,第二块2头->3头(也就是2尾))。
3.我们假定向量A始终在y轴上,我们如果知道了向量B与y轴的角度就能知道答案了,但是,这个角度因为我们假定A始终在y轴上而A一旦变化,B相对坐标轴必须得变化而无法实现,但是我们可以记录B相对把A的方向变成y轴正向时的角度,如果A是指向y正向,那么B相对于A的角度也就是相对与坐标轴的角度,所以B的末尾的坐标就是A的末尾坐标加上B的长度乘以cos/sin(B相对坐标轴的角度(也就是B相对A的角度))得到坐标,但是就白书上给的具体公式而言,我是这么想的:首先题目以-90度为起始点,那么B的角度为180度,所以当输入a时(a为输入的角度),角度从Π变到了a度,变化了a-Π度,但是我们知道,最开始B向量相对坐标轴(同是也是相对于A为y轴正向的时候的角度)(注意,这里不是相对y轴负轴)为90度,现在又变化了a-Π,所以实际上B向量相对于坐标轴变化了a-Π+Π/2 = a - Π/2度,所以应当是cos(a-Π/2),举例子:当有2个线段,把第一个与第二个的夹角变成90度,实际上相对坐标轴时,B向量与坐标轴角度为0(与实际的情况也相同),cos0 = 1, sin0 = 0,所以终点的坐标为(xa(A向量的横轴长度)+ |B|*cos0,ya + |B| *sin0)。
4.3情况是一开始的情况,所以我们必须考虑线段大于两个的情况,如果更新了3的情况后,这个A向量与B向量的矢量和作为结果C,如果C作为别的的B向量,那么C相对坐标轴的夹角就是C更新后的角度,所以直接算即可。但是,如果C向量是新的A向量,那么当我们把新的A摆到y正向时,我们把新的B的末端与原点连成一个向量,记为D,D与x正向所呈的角度相对移动C向量前减少了一定角度,而这个角度等于把C摆到y正向移动的角度,所以新的B相对坐标轴的角度等于新的B之前的角度+A移动的角度,由3情况的黑体公式可得,计算新的B相对坐标轴最新的的角度后套公式也就得到了此段末尾的坐标。
5.当初始段为0,末尾段为n(n的头是n-1的尾)时,也就得到了以0原点时, n - 1的坐标,也就是答案。
//看此代码之前一定要在看看白书的3.3.1的内容和例题,上面的解释可能不太好理解,得画图理解,本题核心就是假定线段A是从(0,0)开始且A向量与y轴正向相同,
//那么B线段的末尾的坐标就等于A的坐标加上向量B与坐标轴x/y轴的分量,所以是A的坐标加上B的坐标*cos/sin(B相对坐标轴的角度),难点在于求解B相对坐标轴的角度,如果更新的在B处,
//那么当B线段只有一段的情况下,是不会更新左右的(因为AB都左右一段,不可能旋转),但在递归出来到更新包含两段的时候,因为AB交界处发生旋转,所以B是相对A变化了角度a的,
//但A仍然平行于y轴正向,所以B相对坐标轴夹角变化了a;当大于两段时,如果变化的节点作为A段,则此时的B段相对的坐标轴的角度等于相对A旋转到y轴正向时角度+A旋转的角度,
//如果作为B段,因为B段已经更新,所以直接计算即可
#include <cstdio> #include <cmath> int n, c; int l[10005], s, ang;//ang对应白书的A数组 double a;//对应白书solve()的double a; struct Node{ double x; double y; double a;//该向量对应与坐标轴的角度,对应白书的ang }node[(1<<15) - 1]; double pre[10005];//对应白书的prv【】 void init(int k, int start, int end) { node[k].x = node[k].a = 0.0; if(end - start == 1) node[k].y = l[start]; else { int chl = k * 2 + 1, chr = k * 2 + 2; int mid = (start + end) / 2; init(chl, start, mid); init(chr, mid, end); node[k].y = node[chl].y + node[chr].y; } } void update(int k, int start, int end) { if(start >= s || end <= s)//如果要更新的点不在现在的范围内(一定要包含,且不为两端点),那么不会该值不会发生改变,所以没必要更新。 return; else { int chl = 2 * k + 1, chr = 2 * k + 2; int mid = (start + end) / 2; update(chl, start, mid); update(chr, mid, end); if(s <= mid)
//如果修改的s在此段作为B向量,那么上次递归时B向量已经更新了,但如果作为A向量,那么B末端相对原点角度会移动一个角度,这个角度等于A变化的角度,所以我们假定A旋转到y正向,
//因为我们只保存B相对A的角度,那么B此时相对坐标轴是原来B相对坐标轴的角度减去A变化的角度,所以得加起来 node[k].a += a - pre[s]; //新的x = A的x + |B| * cos(A变化的角度 + B之前的角度);变化的角度对应node[k].a,之前的对应arccos(node[chr].x / |B|) double sing = sin(node[k].a), cosg = cos(node[k].a); node[k].x = node[chl].x + (cosg * node[chr].x - sing * node[chr].y); node[k].y = node[chl].y + (sing * node[chr].x + cosg * node[chr].y); } } int main() { while(scanf("%d %d", &n, &c) != EOF) { for(int i = 0; i < n; i++) { scanf("%d", &l[i]); pre[i] = acos(-1); } init(0, 0, n); for(int i = 0; i < c; i++) { scanf("%d %d", &s, &ang); a = ang / 360.0 * 2 * acos(-1); update(0, 0, n); pre[s] = a; printf("%.2f %.2f\n", node[0].x, node[0].y); } } return 0; }