题目大意
给出一个长度为
n
n
n的元素两两不同的序列和一个质数
m
m
m。若该序列是由一个等差数列在模
m
m
m意义下打乱而得到的,请尝试求出原数列的首项和公差。若可能的原数列不唯一,请随便输出一个可行数列的首项和公差;若序列不是由任何一个等差数列变换而来的,请输出-1
。
对于
100
%
100\%
100%的数据,
2
≤
m
≤
1
0
9
+
7
2 \leq m \leq 10^9+7
2≤m≤109+7,
1
≤
n
≤
1
0
5
1 \leq n \leq 10^5
1≤n≤105,
0
≤
序
列
中
的
每
一
个
数
<
m
0 \leq 序列中的每一个数<m
0≤序列中的每一个数<m。
要求输出的两个数在
[
0
,
m
)
[0,m)
[0,m)之间。另外,对于《GMOJ-Senior-5449 Pacifist》
,同时要求输出的第二个数不大于
⌊
m
2
⌋
\lfloor \frac{m}{2} \rfloor
⌊2m⌋。
分析
这道题目不考什么困难的算法,但是考脑洞。
对于洛谷-CF763C和CodeForces-763-C
首先,考虑
2
n
≤
m
2n \leq m
2n≤m的情况。记变换前的数列为
s
,
s
+
d
,
s
+
2
d
,
⋯
,
s
+
(
n
−
1
)
d
s,s+d,s+2d, \cdots ,s+(n-1)d
s,s+d,s+2d,⋯,s+(n−1)d,并设
x
x
x为变换前的数列中某两个元素(记为
a
,
b
a,b
a,b,且
a
<
b
a<b
a<b)的差,于是我们有
x
≡
b
−
a
(
m
o
d
m
)
x \equiv b-a \pmod m
x≡b−a(modm)。记
a
a
a在变换前的数列中的位置为
i
i
i,
b
b
b在变换前的数列中的位置为
i
+
k
i+k
i+k,则有
x
≡
d
k
(
m
o
d
m
)
x \equiv dk \pmod m
x≡dk(modm)。当
2
n
≤
m
2n \leq m
2n≤m时,变换后的序列中一定有
(
n
−
k
+
1
)
(n-k+1)
(n−k+1)对数的差为
x
x
x。于是我们可以借助二分查找找出变换后的序列中差为
x
x
x的数对的数量,并由此算出
k
k
k的值。因为
x
≡
d
k
(
m
o
d
m
)
x \equiv dk \pmod m
x≡dk(modm),所以
d
≡
x
k
(
m
o
d
m
)
d \equiv \frac{x}{k} \pmod m
d≡kx(modm);又因为
m
m
m为质数,所以我们可以用费马小定理(
a
p
−
1
≡
1
(
m
o
d
p
)
a^{p-1} \equiv 1 \pmod p
ap−1≡1(modp),其中
p
p
p为质数,且
gcd
(
a
,
p
)
=
1
\gcd(a,p)=1
gcd(a,p)=1)算出
d
d
d。
知道
d
d
d后,我们遍历变换后的序列以寻找合适的
s
s
s。当我们发现对于序列中的某个数
t
t
t满足
(
t
−
d
+
m
)
m
o
d
m
(t-d+m) \mod m
(t−d+m)modm不在序列中,那么它就可能是
s
s
s。但是当我们发现序列中有不止一个数满足时,我们可以确定这个序列不是由任何一个等差数列变换而来的(一个数列的开头只有一个)。
但是,当
2
n
>
m
2n>m
2n>m时,情况不一样了。因为差最大的两组数是
s
,
s
+
(
n
−
1
)
d
s,s+(n-1)d
s,s+(n−1)d以及
s
,
s
+
(
n
−
2
)
d
s,s+(n-2)d
s,s+(n−2)d,它们的差之和为
(
2
n
−
3
)
d
(2n-3)d
(2n−3)d。如果
2
n
−
3
≥
m
2n-3 \geq m
2n−3≥m,那么就会存在两个差,它们的和模
m
m
m为
0
0
0,就会使得我们在查找
序
列
的
后
面
的
项
+
x
序列的后面的项+x
序列的后面的项+x时会找到序列前面的项,就会多统计;但是当
2
n
≤
m
2n \leq m
2n≤m时却不会。这时,我们算出变换后的序列在
{
x
∈
Z
∣
0
≤
x
<
m
}
\{ x \in Z|0 \leq x<m \}
{x∈Z∣0≤x<m}中的补集,并用上述的方法求出补集所对的变换前的等差数列,再将
s
s
s加上
(
m
−
n
)
m
o
d
d
(m-n) \mod d
(m−n)modd。因为补集的大小
n
′
=
m
−
n
<
m
−
m
2
n'=m-n<m-\frac{m}{2}
n′=m−n<m−2m,所以有
2
n
′
<
m
2n'<m
2n′<m,可以使用上述的方法,同时,因为只要
m
m
m与
d
d
d互质,补集所对的原数列就可以为
s
+
n
d
,
s
+
(
n
+
1
)
d
,
s
+
(
n
+
2
)
d
,
⋯
,
s
+
(
m
−
1
)
d
s+nd,s+(n+1)d,s+(n+2)d, \cdots ,s+(m-1)d
s+nd,s+(n+1)d,s+(n+2)d,⋯,s+(m−1)d,那么该序列与补集上得到的原数列可以有同样的公差。
对于GMOJ-Senior-5449
在输出 s , d s,d s,d的基础上,这道题还要求 d ≤ ⌊ m 2 ⌋ d \leq \lfloor \frac{m}{2} \rfloor d≤⌊2m⌋。当 d > ⌊ m 2 ⌋ d> \lfloor \frac{m}{2} \rfloor d>⌊2m⌋时,我们考虑尝试把我们还原出的数列反转。数列一开始为 s , s + d , s + 2 d , ⋯ , s + ( n − 1 ) d s,s+d,s+2d, \cdots ,s+(n-1)d s,s+d,s+2d,⋯,s+(n−1)d,反转后就变成了 s + ( n − 1 ) d , s + ( n − 2 ) d , s + ( n − 3 ) d , ⋯ , s s+(n-1)d,s+(n-2)d,s+(n-3)d, \cdots ,s s+(n−1)d,s+(n−2)d,s+(n−3)d,⋯,s,公差也就由 d d d变成了 − d -d −d。但因为变换后的序列是在模 m m m意义下的,所以 − d -d −d等价于 m − d m-d m−d。此时,因为 d > ⌊ m 2 ⌋ d> \lfloor \frac{m}{2} \rfloor d>⌊2m⌋,所以 m − d < m − ⌊ m 2 ⌋ m-d<m- \lfloor \frac{m}{2} \rfloor m−d<m−⌊2m⌋, m − d ≤ ⌊ m 2 ⌋ m-d \leq \lfloor \frac{m}{2} \rfloor m−d≤⌊2m⌋,符合要求。此时, a n s ans ans也要加上 ( n − 1 ) d (n-1)d (n−1)d。这样, d ≤ ⌊ m 2 ⌋ d \leq \lfloor \frac{m}{2} \rfloor d≤⌊2m⌋的限制就被解决了。
代码
根据思路,可以写出如下代码:
#include<cstdio>
#include<algorithm>
using namespace std;
using ll = long long;
char buf[16777216];
inline ll Read() //快速读入
{
static char* c = buf;
while (*c > '9' || *c < '0')
{
++c;
}
ll ans = *c ^ 48ll;
while (*(++c) >= '0' && *c <= '9')
{
ans = (ans * 10) + (*c ^ 48ll);
}
return ans;
}
ll a[200002], b[200002];
inline bool Find(const ll val) //二分查找给定数值是否在a中
{
ll l = 1, r = a[0];
while (l < r)
{
const ll mid = (l + r) >> 1;
if (a[mid] < val)
{
l = mid + 1;
}
else
{
r = mid;
}
}
return a[r] == val;
}
int main()
{
#ifdef IN_GMOJ
static_cast<void>(freopen("pacifist.in", "r", stdin)); //定义文件输入输出
static_cast<void>(freopen("pacifist.out", "w", stdout));
#endif
fread(buf, 1, 16777216, stdin); //读入所有输入
const ll m = Read(), n = Read(); //读入n和m
if(n == 1) //特判当n=1时的答案
{
printf("%lld 0", Read());
return 0;
}
if(n == m) //特判当n=m时的答案
{
putchar('0');
putchar(' ');
putchar('1');
return 0;
}
for (ll i = 1; i <= n; ++i) //读入变换后的序列
{
a[i] = Read();
}
sort(a + 1, a + (n + 1)); //对序列排序
a[0] = n;
const bool mode = (n * 2 > m);
if (mode) //当2n>m时
{
for (ll i = 0; i < m; ++i) //计算补集
{
if (!Find(i))
{
b[++b[0]] = i;
}
}
for (ll i = 0; i <= b[0]; ++i)
{
a[i] = b[i];
}
}
ll start, d;
if (a[0] == 1) //特判序列长度为1的情况
{
start = a[1];
d = 1;
}
else
{
d = a[2] - a[1]; //寻找x
ll cnt = 0;
for (ll i = 1; i <= a[0]; ++i) //统计变换后的序列中差为x的数对的数量
{
if (Find((a[i] + d) % m))
{
++cnt;
}
}
for (ll t1 = (a[0] - cnt) % m, t2 = m - 2; t2 >= 1; d = ((t2 & 1) == 1 ? d * t1 % m : d), t1 = t1 * t1 % m, t2 >>= 1); //计算d
start = -1;
for (ll t = m - d, i = 1; i <= a[0]; ++i) //寻找s
{
if (!Find((a[i] + t) % m)) //找到可能的s
{
if (start != -1) //如果有多个可能的s
{
start = -1; //无解
break;
}
start = a[i]; //保存s
}
}
if (d > m >> 1) //当d大于m/2时
{
start = (start + (a[0] - 1) * d) % m; //改变s和d
d = m - d;
}
}
if (mode && start != -1) //当2n>m时
{
start = (start + d * b[0]) % m; //改变s
}
printf((start == -1 ? "-1" : "%lld %lld"), start, d); //输出答案
return 0;
}
总结
这道题虽然对算法的难度要求不高,但对思维的要求很高。做对这道题需要很好的思维。