Legend
给定一个 (mod m) 意义下的打乱顺序的等差数列,保证各项不等,请找到它的首项和公差。
或者请输出无解。
保证 (m) 为质数。 (2 le m le 10^9+7),数组长度 (1 le n le 10^5)。
Link ( extrm{to Codeforces})。
Editorial
我们先考虑有解的情况。
题目保证了各项不等,且 (m) 为质数,说明可以通过一次轮换将 (mod m) 的完全剩余系内的数字进行轮换后,使得这个“等差数列”的公差变为 (1)。
举个例子,把 (x=0,1,cdots,4) 带入 (3x+4 pmod 5) 会依次得到:(4,2,0,3,1)。
我们如果对于对应位置的数字进行重新映射:
(4 o 0)
(2 o 1)
(0 o 2)
(cdots)
我们原来公差为 (3) 的等差数列就变成了公差为 (1) 的等差数列,并且这个等差数列在这个新的数域上是连续的。
easy ver
考虑原题目如何进行答案检验:给你一个公差,但不告诉首项,如何检验答案的正确性?
当然,如果你知道了首项是哪一个数字,你就可以一项一项往后面推,就可以 (O(n log n)) 使用 set 检验这个公差是否正确。
但如果不知道首项呢?是不是随机选一个,先一项一项往后面推,再一项一项往前面推,看是不是所有数字都被用到呢?
然而是并不行的,因为先向某一个方向推进可能到了尽头却没有停止,而是不小心使用了另一个方向的数字。
这样子从另一个方向出发的时候就不能经过这个数字,会导致出问题。
不妨用之前的方法先进行映射,再考虑这个问题:
首先我们先按上面的方法将数字重新映射——这样子答案公差就变成了 (1)。不过我们并不知道数字具体是如何映射的,
但我们知道它们被映射到了 (0,1,2,cdots,n-1)。
不过呢,可以发现,当 (2n le m) 的时候是不会出现“数字重复使用”的问题的。因为一次推进最多只会向某一侧跳越 (n-1) 下,
而剩余系中没有被用到的数字是连续的,且没有被用到的数字连续段长度至少有 (m-n),而 (m-n>n-1),所以说推进到不能推进的地方就会停止。
在 (2n le m) 的情况下,我们选定了公差后向两侧推进就可以得到一条链,选择多个不同的起点就可以得到多个不相交的链。
比如说当我们认定公差(映射后)是 (3) 时可能会得到:
(0,3,6,cdots)
(1,4,7,cdots)
(2,5,8,cdots)
这样三条链。
不难发现当选中的公差为几,就会得到多少条不同的链。(前提是公差不大于 (n))
自然,当得到的链数量为 (1) 时,当前的公差便是正确的,否则不正确。
fix
那答案不对的情况下,有没有办法修正呢?
办法是有的,将当前公差除以链的数量就行了。(前提是当前公差不大于 (n))
所以说,在 (2n le m) 的情况下,我们已经知道此题怎么做了:
随机选两个序列中的数字 (x,y),将 (x-y) 当做该等差数列的公差,并按照上述方法提取出链。
如果链的数量为 (k),真正的公差就是 (frac{x-y}{k}),再用这个真正的公差找到首项就行了。
那么,(2n>m) 时如何解决这个问题?
考虑补集转化,我们选择没有出现过的数字来进行这个算法,就可以转化为 (2n le m) 的情况。
我们求得了没有出现过的数字的答案就可以反过来求得原问题的答案了。
具体来说,如果没有出现过的数字的答案,首项是 (st),公差是 (d),则原问题的答案应该是:
首项是 (st-d),公差是 (-d)。
Code
无解也就很好判断了,按照上述算法流程找到公差后,检验一遍答案是否正确即可。
#include <bits/stdc++.h>
#define debug(...) fprintf(stderr ,__VA_ARGS__)
#define __FILE(x)
freopen(#x".in" ,"r" ,stdin);
freopen(#x".out" ,"w" ,stdout)
#define LL long long
const int MX = 1e5 + 23;
int read(){
char k = getchar(); int x = 0;
while(k < '0' || k > '9') k = getchar();
while(k >= '0' && k <= '9') x = x * 10 + k - '0' ,k = getchar();
return x;
}
LL qpow(LL a ,LL b ,LL p){
LL ans = 1;
while(b){if(b & 1) ans = ans * a % p;
a = a * a % p ,b >>= 1;
}return ans;
}
int m ,n ,a[MX] ,A ,B;
bool solve(){
std::set<int> S;
for(int i = 1 ; i <= n ; ++i){
S.insert(a[i]);
}
// std::random_shuffle(a + 1 ,a + 1 + n);
if(n == 1){
A = a[1] ,B = 1;
return 1;
}
int d = (a[2] - a[1] + m) % m;
int spl = 0;
for(int i = 1 ; i <= n ; ++i){
if(S.find(a[i]) != S.end()){
++spl;
for(int j = (a[i] + d) % m ; ; j = (j + d) % m){
if(S.find(j) != S.end()){
S.erase(j);
}
else break;
}
for(int j = a[i] ; ; j = (j - d + m) % m){
if(S.find(j) != S.end()){
S.erase(j);
}
else break;
}
}
}
d = 1LL * d * qpow(spl ,m - 2 ,m) % m;
for(int i = 1 ; i <= n ; ++i){
S.insert(a[i]);
}
for(int i = (a[1] + d) % m ; ; i = (i + d) % m){
if(S.find(i) != S.end()){
S.erase(i);
}
else break;
}
int good = 0;
for(int i = a[1] ; ; i = (i - d + m) % m){
if(S.find(i) != S.end()){
good = i;
S.erase(i);
}
else break;
}
if(S.empty()){
A = good ,B = d;
return 1;
}
else return 0;
}
int main(){
__FILE(CF764E);
m = read() ,n = read();
if(n == m) return puts("0 1") ,0;
for(int i = 1 ; i <= n ; ++i){
a[i] = read();
}
if(n * 2 > m){
std::set<int> S;
for(int i = 1 ; i <= n ; ++i) S.insert(a[i]);
n = 0;
for(int i = 0 ; i < m ; ++i){
if(S.find(i) == S.end()){
a[++n] = i;
}
}
if(solve()){
printf("%d %d
" ,(A - B + m) % m ,(m - B) % m);
}
else puts("-1");
}
else{
if(solve()){
printf("%d %d
" ,A ,B);
}
else puts("-1");
}
return 0;
}