Reading

CodeForces 1045B Space Isaac


Fanyi Pu

原题链接

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

题解

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

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

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

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

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

所以我们令 bi=ai−ai−1(b1=(a1−an)modm),设串 s1=bnbn−1bn−2⋯b1,s2=b1b2b3⋯bn,我们要找的是 s1 与 s2 成环后相等,并找到一对匹配的数,他们加起来模 m 即为一组解。我们令 s3=s2+s2,找到 s3 中所有等于 s1 的子串,就得到了所有解,这个问题用KMP或是Z都能解决。

代码

cpp
#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;
}

Cite this post

@misc{pu2018oiicpccodeforcescf1045b,
  author = {Pu, Fanyi},
  title  = {CodeForces 1045B Space Isaac},
  year   = {2018},
  month  = {9},
  url    = {https://pufanyi.com/blog/oi-icpc/codeforces/cf1045b}
}