题目
分析
首先考虑在怎样的情况下才会重复,设原坐标为 ((x+k_1cdot dx,y+k_1cdot dy)) ,设在 (k_2) 步重复,也就是:
化简一下:(large x+k_1cdot dx=x+k_2cdot dx (mod n))
有: (large (k_1-k_2)cdot dx=0 (mod n))
对于 (y) 来说也是同理,然后因为 (large gcd(dx,n)=gcd(dy,n)=1) ,所以有:(large k_1-k_2=0 (mod n))
也就是说,一个点一定会在经过了 (n) 个点后才会第一次再次来到这个重复点。
这就说明我们选择一个点其实就是选择了一组 (n) 个点,这 (n) 个点内部构成了一个循环。(也就是选择这一组的任何一个点都等价于选了这个组)
同时,上面的条件也说明了每一组点当中的每个点的横纵坐标都互不相同,换而言之就是同一组的点两两不在同一行或者同一列。
那么很容易发现,第 (0) 列的每一个点其实都对应了一组不同点,并且第 (0) 列的所有点构成的所有组包含了所有的分组。
于是我们考虑用 ((0,k)) 作为代表元来表示每一个组的贡献和。
接下来要考虑的就是对于给出的这 (m) 个点,如何快速找到其对应的代表元 ((0,d))
可以看作是从这个点走对应的环,直到走到 ((0,d)) 这样的形式。
也就是需要快速求出这样的 (Dy) ,满足 (Dy=kcdot dy+y,kcdot dx+x=0) 。
(dy,y,dx,x) 都已知,于是求出 (k) 就能求出 (Dy) 。
这里显然是可以直接 (exgcd) 求出 (dx) 对于 (n) 的逆元,其实也就是预处理一下就行,然后就随便求 (k) 了。
但是显然我们也可以先直接预处理对于 (k=[1,n]) 的所有 (-x=kcdot dx) ,需要的时候直接从 (-x) 里面调用这个 (k) 即可。(其实就是个逆映射)
最后就是在所有组当中选择最大的那个组即可,输出点就直接输出代表元就行了。
时间复杂度 (O(n)) 。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define inc(x,y,mod) (((x)+(y))>=(mod)?(x)+(y)-(mod):(x)+(y))
#define dec(x,y,mod) ((x)-(y)<0?(x)-(y)+(mod):(x)-(y))
#define rep(i,x,y) for(int i=(x);i<=(y);i++)
#define dep(i,y,x) for(int i=(y);i>=(x);i--)
const int N=1e6+5,M=2e5+5,MOD=1e9+7;
int n,m,dx,dy,a[N],Ans[N],Maxn,ex,ey;
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m>>dx>>dy;
for(register int i=1,x=0,y=0;i<=n;i++) a[x]=y,x=(x+dx)%n,y=(y+dy)%n;
for(register int i=1,x,y;i<=m;i++){
cin>>x>>y;
y=(y-a[x]+n)%n;
Ans[y]++;
if(Ans[y]>Maxn) Maxn=Ans[y],ex=0,ey=y;
}
cout<<ex<<' '<<ey;
return 0;
}