抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

原题链接

0∼m−10\sim m-1的数被分成两个集合,你可以分别从两个集合中取一个数相加并对 mm 取模,求 0∼m−10\sim m-1 中不能构造出的数。

题解

感觉如果 sxd666 来做这题肯定能一眼秒,然而他正忙着切其他题。

首先我们发现如果要让 a+b≡x(modm)​a + b \equiv x \pmod m​,如果已知a,x​a, x​,那b​b​ 一定是唯一的。也就是说,假设给定集合是 A​A​,与之对应的集合为B​B​,如果有a∈A​a\in A​ 但找不到 b∈A​b\in A​ 使得 a+b≡x(modm)​a + b \equiv x\pmod m​。那么x∈A+B​x\in A + B​(定义A+B={a+b:a∈A,b∈B}​A + B = \{a + b : a\in A, b\in B\}​)。反过来讲,如果x∉A+B​x\notin A + B​,那么一定能把A​A​ 中所有元素配对(可能两个数相同),也即x∉A+B  ⟺  A=x−A​x\notin A + B \iff A = x - A​(定义x−A={x−a:a∈A}​x - A= \{x - a : a\in A\}​)。

然后我们如果把小于 mm 的整数看成一个环,如果有两个数 a,ba, b 使a+b≡x(modm)a + b \equiv x \pmod m,aa顺时针时针移动,bb肯定逆时针移动(即运动方向相反,且移动的长度应该是相等的((a+k) mod m+(b−k) mod m≡a+b(modm)(a + k)\bmod m + (b - k)\bmod m \equiv a + b \pmod m嘛)。

于是我们画两个圆,都表示集合 {ai}\{a_i\}(假设aia_i 已经排好序),我们要把第一个圆的点与第二个圆的点匹配。

假设 aia_i 与aja_j匹配。我们把 ii 移动至 i+1i+1,那么根据上面推出的单调性,jj 必须移至 j−1j-1(因为ai∼ai+1a_i\sim a_{i+1} 之间没有数了,所以 jj 也只能移动一格),又因为移动距离必须相等,即ai+1−ai=aj−aj−1a_{i+1} - a_i = a_j - a_{j-1}。

所以我们令 bi=ai−ai−1b_i = a_{i} - a_{i-1}(b1=(a1−an) mod mb_1 = (a_1 - a_n)\bmod m),设串s1=bnbn−1bn−2⋯b1,s2=b1b2b3⋯bns_1 = b_nb_{n-1}b_{n-2}\cdots b_1, s_2 = b_1b_2b_3\cdots b_n,我们要找的是s1s_1 与s2s_2成环后相等,并找到一对匹配的数,他们加起来模 mm 即为一组解。我们令 s3=s2+s2s_3 = s_2 + s_2,找到s3s_3 中所有等于 s1s_1 的子串,就得到了所有解,这个问题用 KMP 或是 Z 都能解决。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <cstdio>
#include <set>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int maxn = 200005;

LL aa[maxn]; // 读入的 a
LL bb[maxn]; // 即上面说的 b
vector<LL> gou;
int in[maxn << 2];
LL Z[maxn << 2];
set<LL> ans;

int main()
{
int n;
LL m;
scanf("%d%lld", &n, &m);
for(int i = 1; i <= n; ++i)
scanf("%lld", &aa[i]);
bb[1] = ((aa[1] - aa[n]) + m) % m;
for(int i = 2; i <= n; ++i)
bb[i] = ((aa[i] - aa[i-1]) % m + m) % m;
for(int i = n; i; --i) // 这里用的是 Z 算法,所以合并成了一个串
{
gou.push_back(bb[i]);
in[gou.size() - 1] = i;
}
gou.push_back(-1LL);
for(int i = 1; i <= n; ++i)
{
gou.push_back(bb[i]);
in[gou.size() - 1] = i;
}
for(int i = 1; i <= n; ++i)
{
gou.push_back(bb[i]);
in[gou.size() - 1] = i;
}
Z[0] = gou.size();
for(int i = 1, j = 1, k; i < (int) gou.size(); i = k) // Z 算法
{
j = max(j, i);
while(gou[j] == gou[j - i])
++j;
Z[i] = j - i;
k = i + 1;
while(k + Z[k - i] < j)
{
Z[k] = Z[k - i];
++k;
}
}
for(int i = 1; i < (int) gou.size(); ++i)
if(Z[i] >= n) // 大力记录答案
ans.insert((aa[in[i] - 1 ? in[i] - 1 : n] + aa[n]) % m);
printf("%d\n", (int) ans.size());
for(auto it = ans.begin(); it != ans.end(); ++it)
printf("%lld ", *it);
return 0;
}

评论