超级马——问题的放大与缩小
CDFLS_mao_zx · · 题解
超级马
超级马
建议的前置知识
如果在阅读时感到困惑可以阅读这些链接中可能需要的前置知识。
群论简介 OI-WIKI:该链接下全部内容。
线性代数 OI-WIKI:该部分特征多项式部分前的全部内容。
小凯的疑惑:弄懂基于裴蜀定理的证明。
大体思路
两种主要的方向:图论和数学,图论做法的复杂度至少是
正整数集合的代数性质很差,不妨先考虑整数集合。
下面从两个角度来考量。
线性代数的角度
一个二维的线性空间只需要两个基就能生成,但这道题中的空间并非线性空间,整数集合仅仅是交换幺环,不是域,但我们同样可以从基的角度去考虑。
记
考虑对某一维运用辗转相减或者说辗转相除,最后该维只能有一个非零元素,而且这个非零元素必须为
对两维同时运用该算法即可判别是否可以在允许可逆操作的前提下到达。
多元一次方程的角度
达到任意点的条件等价于能够到达
求这个方程组的整数解也是困难的,我们不妨考虑将第二项作为限制,看第一项能得到什么。
在将第二项作为限制的前提下,记第一项能得到的数的集合为
将
证明思路是证明如果存在整数解,那么一定将整体的求和拆分为若干个
回归原问题
问题变成了已知在操作可逆的前提下能到达所有点,问是否存在一种方式使得在操作不可逆的前提下也能到达所有点。
如果所有的向量都落在同一个半平面内,那么显然不能到达所有点,否则,在每一条坐标轴的两个方向上都能找到一个点能够通过正向操作到达(两个向量只使用不涉及减法的的线性变换能得到的向量落在其小于
于是
因此对所有坐标极角排序即可。
有一个 STL 函数叫 atan2(double x,double y),能够在给定
从方程组的角度看,可以改变一下
总结
问题的第一步是将问题 "放大" 到整数集合,以便更好的利用其生成空间的代数性质。
第二步是利用这些代数性质得到必要条件后再回到原问题考虑。
这样能够简化问题的核心原因是相比于原问题,我们解决问题的每一步都凭空多出了一些条件——第一部分我们可以更好的利用整数环下线性空间的性质,第二部分可以利用第一部分得到的必要条件。
凭空多出条件也是反证法和数学归纳法如此强大的原因。
代码
// 线性代数角度
#include <bits/stdc++.h>
using namespace std;
const int maxn=110;
const double pi=atan2(0,-1);
int n,i,x,y,T; double d;
struct vec{
int x,y; double at;
inline bool operator <(const vec &a)const{return at<a.at;}
}N[maxn],a,b,t;
int main(){
scanf("%d",&T);
st: while(T--){
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%d%d",&x,&y);
N[i]={x,y,atan2(y,x)};
}
if(n<=2){printf("No\n");goto st;}
sort(N+1,N+n+1); N[n+1]=N[1];
for(i=1;i<=n;++i){
if((d=N[i+1].at-N[i].at)<0) d+=2*pi;
if(d>=pi){printf("No\n");goto st;}
}
for(a=N[1],i=2,y=0;i<=n;++i){
b=N[i];
while(b.x){
t=a; a=b; x=t.x/b.x;
b={t.x-x*b.x,t.y-x*b.y,0};
}
y=__gcd(y,b.y);
}
if(abs(a.x)!=1||abs(y)!=1) printf("No\n");
else printf("Yes\n");
}
return 0;
}
方程组角度
#include<bits/stdc++.h>
#define LL long long
using namespace std;
void read(int &x){
x=0;char ch=getchar();int f=1;
while((ch<'0'||ch>'9')&&ch!=45)ch=getchar();
if(ch==45)ch=getchar(),f=-1;
while(ch<='9'&&ch>='0')x=x*10+ch-'0',ch=getchar();
x*=f;
}
template<typename T1,typename T2>void cmax(T1 &x,const T2 &y){if(y>x)x=y;}
template<typename T1,typename T2>void cmin(T1 &x,const T2 &y){if(y<x)x=y;}
const int N=3005;
int i,j,k,m,n,s,t;
int g[N][N],p[105],q[105],ap[105],aq[105];
int _gcd(int a,int b){
return g[a][b]?g[a][b]:(!b?a:_gcd(b,a%b));
}
int fgcd(int a,int b){
if(a<N&&b<N)return g[a][b];
return !b||!a?max(a,b):fgcd(b,a%b);
}
bool check(){
int f=0,pd[2]={};
for(i=1;i<=n;i++){
for(j=1;j<i;j++)if((p[i]||p[j])){
int final;
if(p[i]*p[j]>0)final=q[i]*ap[j]+q[j]*-ap[i];
else final=q[i]*ap[j]+q[j]*ap[i],pd[0]|=final>0,pd[1]|=final<0;
final/=fgcd(ap[i],ap[j]);
f=fgcd(f,abs(final));
}
if(pd[0]&&pd[1]&&f==1)return 1;
}
return 0;
}
void _solve(){
read(n);
for(i=1;i<=n;i++)read(p[i]),read(q[i]),ap[i]=abs(p[i]),aq[i]=abs(q[i]);
int f=1;
f&=check();
if(!f)return void(puts("No"));
swap(p,q);swap(ap,aq);
f&=check();
puts(f?"Yes":"No");
}
signed main(){
for(i=0;i<N;i++)for(j=0;j<=i;j++)g[i][j]=_gcd(i,j);
for(i=0;i<N;i++)for(j=i;j<N;j++)g[i][j]=g[j][i];
int T;read(T);
while(T--){
_solve();
}
return 0;
}
参考资料
IOI2004 国家集训队论文 《转化目标在解题中的应用》——栗师
Cry_For_theMoon 大佬的博客