Timus 1383. Flower-garden Designs 题解
Timus 1383. Flower-garden Designs 题解
题意:
给你一些点集\(P_1,P_2,P_3...P_n\),你需要找到它们的等价类。
两个点集\(P_1,P_2\)是等价的,当且仅当:
- \(|P_1|=|P_2|\)
- 存在一个矩阵\(A\),满足\(\det (A)>0\)。将\(P_1\)中所有的向量乘上\(A\)得到\(P_2\)。(也就是说对\(P_1\)做平移,拉伸或旋转,不能翻折,能得到\(P_2\))
题解:
先考虑如何判定\(P_1,P_2\)是否等价。
设\(P_1,P_2\)中的点为\(A_1,A_2,A_3...A_m\),\(B_1,B_2...B_m\)。
首先除去一个特殊情况:\(P_1\)或\(P_2\)中的点共线,这个非常好处理,直接记录相邻段的比值,然后除去\(gcd\),哈希判断就行了。
下面讨论不共线的情况:
由于只涉及到旋转和平移,所以很自然的可以想到找到点集的重心:\(O\),然后将所有点表示成与\(O\)的相对位置,\(A_i\leftarrow A_i-O_A\),\(B_i\leftarrow B_i-O_B\)。
然后将它们移动到同一个直角坐标系中
由于不能翻转,所以我们可以先极角排序,这样向量之间的顺序就不会改变。
一种\(M^2\)的做法:
枚举对应关系\(A_iB_j,A_{i+1}B_{j+1}...\)(下标在模意义下)。
随便取出不共线的两组就可以解出矩阵\(A\)了。
优化:
考虑计算两两间的叉积:
\(A_i*A_j,B_i*B_j\)
可以发现其中一个必定是另一个的倍数,且倍数固定,这个倍数恰好就是\(\det(A)\),是一个定值。
如果我们记录两两间的叉积,然后判断是否循环同构,只是一个必要条件,只能说明相邻两个向量解出的矩阵的行列式相同。
但是判定相等必须拥有至少\(2n\)个值,所以可以记录\(A_i和A_{i+1},A_{i+2}\) 的叉积,有了\(2n\)个值,如果这\(2n\)个值循环同构的话,就可以说明算出的矩阵都是一样的了。
证明:
由于进行线性变换后两个向量的叉积会乘上\(\det(M)\),\(M\)是线性变换的矩阵:
其中叉积是左边矩阵的\(\det\)。
所以叉积结果必须成比例,这是个必要条件。
如果将一个叉积结果除去这个比例,可以理解成消除了变换的影响。
剩下的只需要比较叉积序列是否循环同构就可以了,由于有\(2n\)个变量(每个坐标\((x,y)\)看作两个变量),所以存储\(2n\)个叉积信息就足够了。
实现细节:
- 将共线的情况特判掉。
- 如果\(A_i,A_{i+1},A_{i+2}\)都共线,则需要将\(A_i,A_{i+2}\)的叉积替换成\(A_{i},A_j\)的叉积(\(j\)是\(i\)后面第一个和\(A_{i},A_{i+1}\)不共线的点)
- 下标最好都\(\times n\),这样就可以全整数运算,避免精度错误。
- 如果刚好有一个点在重心上,需要额外记录一个信息。
code
typedef pair<int,int> vec;
#define x FIR
#define y SEC
int cross(vec A,vec B){
return A.x*B.y-A.y*B.x;
}
int dot(vec A,vec B){
return A.x*B.x+A.y*B.y;
}
vec operator - (vec A,vec B){
return II(A.FIR-B.FIR,A.SEC-B.SEC);
}
vec operator + (vec A,vec B){
return II(A.x+B.x,A.y+B.y);
}
const int MAXN=1e4+233;
int n;
map<pair<int,int>,vector<int> > clss;
const int MOD=1e9+7;
const int MUL=232333;
int quick(int A,int B){
int ret=1;
while(B){
if(B&1) ret=1ll*ret*A%MOD;
B>>=1;
A=1ll*A*A%MOD;
}
return ret;
}
int inv(int A){return quick(A,MOD-2);}
double ang(vec VV){
return atan2(VV.y,VV.x);
}
bool cmp(vec A,vec B){
if(abs(atan2(A.y,A.x)-atan2(B.y,B.x))<1e-8) return A.x*A.x+A.y*A.y<B.x*B.x+B.y*B.y;
return atan2(A.y,A.x)<atan2(B.y,B.x);
}
int Hash(vector<int> v){
int g=v.front();
for(auto it:v){
g=__gcd(g,it);
}
g=abs(g);
for(auto & it:v) it/=g;
int Tmp=1;
int ret=0;
for(auto it:v){
ret+=1ll*Tmp*it%MOD;
ret%=MOD;
Tmp=1ll*Tmp*MUL%MOD;
}
return ret;
}
int Small_Represent(vector<int> v){
int g=*max_element(ALL(v));
for(auto it:v){
if(it) g=__gcd(g,it);
}
g=abs(g);
for(auto & it:v) it/=g;
int siz=v.size();
int ret=MOD;
rep(i,siz) v.PB(v[i]);
int Tmp=0;
int mull=1;
rep(i,siz){
(Tmp+=1ll*mull*v[i]%MOD)%=MOD;
mull=1ll*mull*MUL%MOD;
}
mull=1ll*mull*inv(MUL)%MOD;
ret=Tmp;
rb(i,1,siz-1){
Tmp-=v[i-1];
Tmp+=MOD;
Tmp%=MOD;
Tmp=1ll*Tmp*inv(MUL)%MOD;
Tmp+=1ll*mull*v[i+siz-1]%MOD;
Tmp%=MOD;
check_min(ret,Tmp);
}
return ret;
}
void work(vector<vec> v,int id){
if(v.size()==1){
clss[II(2,0)].PB(id);
return ;
}
sort(ALL(v));
bool line=1;
if(v.size()==2);
else{
rb(i,2,v.size()-1){
if(cross((v[i]-v[0]),(v[i-1]-v[0]))) line=false;
}
}
if(line){
vector<int> Tmp1,Tmp2;
if(v[0].x==v[1].x)
rb(i,1,v.size()-1) Tmp1.PB(v[i].y-v[i-1].y);
else
rb(i,1,v.size()-1) Tmp1.PB(v[i].x-v[i-1].x);
Tmp2=Tmp1;
reverse(ALL(Tmp2));
clss[II(1,min(Hash(Tmp1),Hash(Tmp2)))].PB(id);
return ;
}
vector<int> V;
vector<vec> newv;
vec heavy=II(0,0);
for(auto it:v){
heavy=heavy+it;
}
for(auto & it:v){
it.FIR*=v.size();
it.SEC*=v.size();
if(it!=heavy) newv.PB(it-heavy);
}
sort(ALL(newv),cmp);
int siz=newv.size();
rep(i,siz) newv.PB(newv[i]);
int j=0;
rep(i,siz){
while(abs((ang(newv[j])-ang(newv[i])))<1e-8)++j;
V.PB(cross(newv[i],newv[i+1]));
V.PB(cross(newv[i],newv[j]));
}
clss[II(0,1ll*Small_Represent(V)*v.size()%MOD)].PB(id);
}
signed main(){
scanf("%lld",&n);
rb(i,1,n){
int sz;
vector<vec> v;
scanf("%lld",&sz);
rb(j,1,sz){
vec tmp;
scanf("%lld%lld",&tmp.x,&tmp.y);
v.PB(tmp);
}
work(v,i);
}
cout<<clss.size()<<endl;
for(auto it:clss){
for(auto itt:it.SEC){
printf("%lld ",itt);
}
puts("0");
}
return 0;
}