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

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


了解详情 >

原题链接

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

题解

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

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

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

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

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

所以我们令 bi=aiai1b_i = a_{i} - a_{i-1}b1=(a1an)modmb_1 = (a_1 - a_n)\bmod m),设串s1=bnbn1bn2b1,s2=b1b2b3bns_1 = b_nb_{n-1}b_{n-2}\cdots b_1, s_2 = b_1b_2b_3\cdots b_n,我们要找的是s1s_1s2s_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;
}

评论