【题解&比赛总结】图形变换

该博客介绍了如何使用矩阵乘法来解决图形变换问题,包括平移、缩放和旋转。博主通过给出详细的题解,解释了如何构建矩阵来处理这些变换,并提到了在处理旋转时需要注意的角度方向。此外,还提供了样例输入和输出,以及数据约束和提示,强调了处理旋转角度和系统定义的pi值的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

好久没有打题解了,今天做了一套好题,收获良多。

Description

翔翔最近接到一个任务,要把一个图形做大量的变换操作,翔翔实在是操作得手软,决定写个程序来执行变换操作。
翔翔目前接到的任务是,对一个由n个点组成的图形连续作平移、缩放、旋转变换。相关操作定义如下:
Trans(dx,dy)Trans(dx,dy)Trans(dx,dy) 表示平移图形,即把图形上所有的点的横纵坐标分别加上dx和dy;
Scale(sx,sy)Scale(sx,sy)Scale(sx,sy) 表示缩放图形,即把图形上所有点的横纵坐标分别乘以sx和sy;
Rotate(θ,x0,y0)Rotate(θ,x0,y0)Rotate(θ,x0,y0) 表示旋转图形,即把图形上所有点的坐标绕(x0,y0)顺时针旋转θ角度
由于某些操作会重复运行多次,翔翔还定义了循环指令:
Loop(m)Loop(m)Loop(m)

EndEndEnd
表示把Loop和对应End之间的操作循环执行m次,循环可以嵌套。

Input

第一行一个整数n(n<=100)表示图形由n个点组成;
接下来n行,每行空格隔开两个实数xi,yi表示点的坐标;
接下来一直到文件结束,每行一条操作指令。保证指令格式合法,无多余空格。

Output

输出有n行,每行两个空格隔开实数xi,yi表示对应输入的点变换后的坐标。
本题采用Special Judge判断,只要你输出的数值与标准答案误差不能超过1即可。

Sample Input

3
0.5 0
2.5 2
-4.5 1
Trans(1.5,-1)
Loop(2)
Trans(1,1)
Loop(2)
Rotate(90,0,0)
End
Scale(2,3)
End

Sample Output

10.0000 -3.0000
18.0000 15.0000
-10.0000 6.0000

Data Constraint

保证操作中坐标值不会超过double范围,输出不会超过int范围;
指令总共不超过1000行;
对于所有的数据,所有循环指令中m<=1000000;
对于60%的数据,所有循环指令中m<=1000;
对于30%的数据不含嵌套循环。

Hint

【友情提醒】
pi的值最好用系统的值。C++的定义为:#define Pi M_PI
Pascal就是直接为:pi
不要自己定义避免因为pi带来的误差。

题解

答案只跟 x,yx,yx,y 的若干次运算有关,我们可以考虑用矩阵乘法(仿射变换)来模拟这个东西。
我们设一个初始矩阵Ans=(xy1)\begin{pmatrix} x & y & 1\\ \end{pmatrix}(xy1)
首先考虑平移的情况:
要想转移到(x+dxy+dy1)\begin{pmatrix} x+dx & y+dy & 1\\ \end{pmatrix}(x+dxy+dy1)只需要乘上一个矩阵(100010dxdy1)\begin{pmatrix} 1 & 0& 0\\ 0 & 1 &0 \\ dx&dy & 1 \end{pmatrix}10dx01dy001
缩放:
要想转移到(x⋅sxy⋅dy1)\begin{pmatrix} x\cdot sx & y\cdot dy & 1\\ \end{pmatrix}(xsxydy1)只需要乘上一个矩阵(sx000sy0001)\begin{pmatrix} sx& 0& 0\\ 0 & sy&0 \\ 0&0 & 1 \end{pmatrix}sx000sy0001
旋转就稍微麻烦一些了
首先,我们得知道一个公式,即一个点绕原点逆时针旋转 θ\thetaθ 后的坐标
在这里插入图片描述
由于题目要求顺时针,把 θ\thetaθ 取反即可
然后构建出矩阵(cosθsinθ0−sinθcosθ0001)\begin{pmatrix} cos\theta & sin\theta & 0 \\ -sin\theta & cos\theta & 0 \\ 0 & 0 & 1 \end{pmatrix}cosθsinθ0sinθcosθ0001因为这是在原点的情况,我们需要先将点平移到原点,旋转,再平移回去,即:
(100010−x0−y01)\begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ -x_0 & -y_0 & 1 \end{pmatrix}10x001y0001 (cosθsinθ0−sinθcosθ0001)\begin{pmatrix} cos\theta & sin\theta & 0 \\ -sin\theta & cos\theta & 0 \\ 0 & 0 & 1 \end{pmatrix}cosθsinθ0sinθcosθ0001 (100010x0y01)\begin{pmatrix} 1 & 0 & 0 \\ 0 & 1 & 0 \\ x_0 & y_0 & 1 \end{pmatrix}10x001y0001 === (cosθsinθ0−sinθcosθ0x−xcosθ+ysinθy−ycosθ−xsinθ1)\begin{pmatrix} cos\theta & sin\theta & 0 \\ -sin\theta & cos\theta & 0 \\ x-xcos\theta+ysin\theta & y-ycos\theta-xsin\theta & 1 \end{pmatrix}cosθsinθxxcosθ+ysinθsinθcosθyycosθxsinθ001
然后遇到循环用一个快速幂优化一下就可以了。

Code

#include<cstdio>
#include<math.h>
#include<cmath>
#include<iostream>
#define pi M_PI
using namespace std;
struct matrix{
	double M[4][4];
};
matrix a,a2[1005],ans[105];
int n,top,cnt;
int st[1005];
void empty(matrix &x){
	x.M[1][1]=1;x.M[1][2]=0;x.M[1][3]=0;
	x.M[2][1]=0;x.M[2][2]=1;x.M[2][3]=0;
	x.M[3][1]=0;x.M[3][2]=0;x.M[3][3]=1;
	return;
}
void Trans(matrix &x,double xx,double yy){
	x.M[1][1]=1;x.M[1][2]=0;x.M[1][3]=0;
	x.M[2][1]=0;x.M[2][2]=1;x.M[2][3]=0;
	x.M[3][1]=xx;x.M[3][2]=yy;x.M[3][3]=1;
	return;
}
void Scale(matrix &x,double xx,double yy){
	x.M[1][1]=xx;x.M[1][2]=0;x.M[1][3]=0;
	x.M[2][1]=0;x.M[2][2]=yy;x.M[2][3]=0;
	x.M[3][1]=0;x.M[3][2]=0;x.M[3][3]=1;
	return;
}
void Rotate(matrix &x,double xx,double yy,int zz){
	double si=sin(zz/180.0*pi),co=cos(zz/180.0*pi);
	x.M[1][1]=co;x.M[1][2]=si;x.M[1][3]=0;
	x.M[2][1]=-si;x.M[2][2]=co;x.M[2][3]=0;
	x.M[3][1]=xx-xx*co+yy*si;x.M[3][2]=yy-xx*si-yy*co;x.M[3][3]=1;
	return;
}
matrix time(matrix x,matrix y){
	matrix T;
	for (int i=1;i<=3;++i)
		for (int j=1;j<=3;++j)
			T.M[i][j]=0;
	for (int i=1;i<=3;++i)
		for (int j=1;j<=3;++j)
			for (int k=1;k<=3;++k)
				T.M[i][j]+=x.M[i][k]*y.M[k][j];
	return T;
}
matrix ksm(matrix A,int b){
	matrix R;
	empty(R);
	for (;b;b>>=1){
		if (b&1) R=time(R,A);
		A=time(A,A);
	}
	return R;
}
int main(){
	freopen("transform.in","r",stdin);
	freopen("transform.out","w",stdout); 
	scanf("%d",&n);
	for (int i=1;i<=n;++i)
		scanf("%lf%lf",&ans[i].M[1][1],&ans[i].M[1][2]),ans[i].M[1][3]=1;
	st[0]=1;
	empty(a2[0]);
	int times,jd;
	double p,q;
	char ch;
	while (cin>>ch){
		while (ch<'A'||(ch<'a'&&ch>'Z')||ch>'z') ch=getchar();
		if (ch=='T'){
			while(ch!='(') ch=getchar();
			scanf("%lf",&p);ch=getchar();scanf("%lf",&q);ch=getchar();
			Trans(a,p,q);
			if (top==0){for(int i=1;i<=n;++i)ans[i]=time(ans[i],a);}
			else a2[top]=time(a2[top],a);
		}
		if (ch=='S'){
			while(ch!='(') ch=getchar();
			scanf("%lf",&p);ch=getchar();scanf("%lf",&q);ch=getchar();
			Scale(a,p,q);
			if (top==0){for(int i=1;i<=n;++i)ans[i]=time(ans[i],a);}
			else a2[top]=time(a2[top],a);
		}
		if (ch=='R'){
			while(ch!='(') ch=getchar();
			scanf("%d",&jd);ch=getchar();scanf("%lf",&p);ch=getchar();scanf("%lf",&q);ch=getchar();
			jd=-jd;
			Rotate(a,p,q,jd);
			if (top==0){for(int i=1;i<=n;++i)ans[i]=time(ans[i],a);}
			else a2[top]=time(a2[top],a);
		}
		if (ch=='L'){
			while(ch!='(') ch=getchar();
			scanf("%d",&times);ch=getchar();
			if (top==0){
				for (int i=1;i<=n;++i)ans[i]=time(ans[i],a2[top]);
				empty(a2[top]);
			}
			st[++top]=times;
			empty(a2[top]);
		}
		if (ch=='E'){
			ch=getchar();ch=getchar();
			if (top==1){
				matrix t=ksm(a2[top],st[top]);
				for(int i=1;i<=n;++i)ans[i]=time(ans[i],t);
			}
			else{
				matrix t=ksm(a2[top],st[top]); 
				a2[top-1]=time(a2[top-1],t);
			}
			empty(a2[top]);
			--top;
		}
	}
	for (int i=1;i<=n;++i)
		printf("%.5lf %.5lf\n",ans[i].M[1][1],ans[i].M[1][2]); 
	return 0;
}

恶心的读入。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值