「解题报告」[AGC022F] Checkers

题目大意

\(x=10^{100}\),在数轴上有 \(n\) 个点,第 \(i\) 个点的坐标为 \(x^i\),每次可以将一个点 \(A\) 变为关于点 \(B\) 的对称点,并把 \(B\) 删除,进行 \(n-1\) 次这样的操作,问最后能得到多少种不同的坐标。

\(n \le 50\)

去看了官方的题解,这里给出官方的 \(O(n^4)\) 做法和比较完整的证明。

思路

首先,\(x\) 特别大,我们可以直接把每个 \(x^i\) 看作是互相独立的。\(A\) 关于 \(B\) 的对称点实际上就是 \(2B-A\)

首先观察可以发现,得到的所有数肯定都是长成 \(\sum 2^a(-1)^bx^i\) 的形式。

举个例子,比如最后我们得到了 \(2x^1+2x^2+x^3-4x^4\),那么我们发现其实只需要把点的顺序调换一下,就可以得到另外一种结果,比如 \(2x^1+2x^3+x^2-4x^4\)

所以我们其实只关心最后得到的系数的集合。上面的例子中,我们只关心最后得到的系数集合 \(\{1, 2, 2, -4\}\)

我们寻找这样的系数集合 \(A\) 都有什么性质:

  1. \(\forall i\in A,\, \exists j,\, \rvert i\lvert=2^j\),即集合中每个元素的绝对值都是 \(2\) 的次幂。

    证明:考虑对称的过程 \(2B-A\),那么实际上每个数就是被乘上了若干个 \(2\)

  2. \(\sum_{i\in A}i=1\),即集合中元素的和为 \(1\)

    证明:考虑归纳法证明。

    首先初始值 \(\{1\}\) 的和为 \(1\)

    \(A,B\) 集合均满足以上条件,那么新得到的集合 \(2B-A\) 的和为 \(2 \times 1 - 1 = 1\),得证。

  3. \(2^i\)\(-2^i\) 属于 \(A\),那么 \(2^{i-1}\)\(-2^{i-1}\) 也属于 \(A\)

    证明:同样归纳法证明,如果 \(A\) 包含的指数范围为 \([0,a]\)\(B\) 包含的指数范围为 \([0,b]\),那么易证 \(2B-A\) 包含的指数范围为 \([0,\max(a,b+1)]\)

  4. \(2^0\)\(-2^0\) 中有一个且仅有一个属于 \(A\)

    证明:归纳法,首先 \(2^0,-2^0\) 都不属于 \(2B\),而 \(A\) 中有 \(2^0,-2^0\) 中的其中一个,所以 \(2B-A\) 中有且仅有 \(2^0,-2^0\) 中的一个。

但是这些性质都还只是必要条件,并不充分。我们有一个充要条件如下:

对于每一个 \(i>1\),存在一种将 \(A\) 中的所有 \(\pm 2^i\) 元素前的正负号进行修改的方案,使得 \(\sum_{\rvert x\lvert=2^j,j\in[0,i]}x=1\)(即所有指数小于等于 \(i\) 的数的和为 \(1\)

证明:

  1. 必要性:

    归纳法。

    然后对于每一个 \(i>1\),将 \(B\) 集合中的 \(2^{i-1}\) 元素重标正负号后,得到指数范围在 \([0,i-1]\) 内的数的和为 \(1\),并将 \(A\) 集合中的 \(2^i\) 元素重标正负号后,得到指数范围在 \([0,i]\) 内的数的和为 \(1\)

    那么 \(2B\) 的指数范围就是 \([1,i]\),且 \(2^i\) 的正负号被重标,那么 \(2B-A\) 就也符合上述性质。

    对于 \(i=1\),根据性质 \(4\),一定可以进行重标正负号使得和为 \(1\)

  2. 充分性:

    我们想办法构造出一种方案,使得 \(C\) 集合能够被分为 \(2B-A\),并且 \(A,B,C\) 集合均满足以上的所有性质,即可证明充分性。


    首先假设 \(-2^0 \in C\)

    \(k\) 为最小的正整数使得 \(2^k\in C\)。如果对于指数范围在 \([1,k-1]\) 内的所有数都出现了大于等于两次,我们可以构造 \(B=\{-2^0,-2^1,\cdots,-2^{k-2},2^{k-1}\}\),这样 \(2B=\{-2^1,-2^2,\cdots,-2^{k-1},2^k\}\),我们从 \(C\) 集合中刨除掉这些元素,再将元素取反,就得到了 \(A\) 集合。

    首先可以发现 \(B\) 集合满足以上的所有性质,并且 \(A\) 集合满足以上的 \(4\) 条基本性质。


    下面是 \(A\) 集合满足性质 \(5\) 的证明:

    首先对于所有的 \(i\le k\)\(C\) 集合中的 \(\pm2^i\) 在重标正负号后,\(2^i\) 的个数一定大于 \(-2^i\) 的个数,否则和 \(<0\),不满足性质 \(5\)

    那么在重标正负号的结果中,一定存在一个 \(2^i\)。我们将这个 \(2^i\) 分配给 \(B\) 集合,将剩下的分配给 \(A\) 集合,那么可以证明 \(i\le k\) 的时候性质 \(5\) 是成立的。

    对于 \(i > k\)\(C\) 集合重标正负号的结果都分配给了 \(A\),因为 \(C\) 集合的和为 \(1\)\(B\) 集合的和也为 \(1\),那么 \(A\) 集合的和肯定也为 \(2\times 1 - 1 = 1\),所以也是成立的。

    综上,这样构造出来的 \(A\) 集合是满足性质 \(5\) 的。


    如果对于指数范围在 \([1,k-1]\) 内,存在一个数只出现了一次,那么我们发现再按照上面的方式分配,会导致得到的 \(A\) 集合不满足性质 \(3\)

    那么我们记最小的只出现了一次的指数为 \(j\)。如果 \(j=1\),那么我们可以令 \(A=\{2^0\}\),并且 \(B\) 为剩下的数除以 \(2\)。对于 \(C\) 的每一个前缀和,将它减去第一个元素 \(-2^0\) 再除以 \(2\) 也等于 \(1\),所以 \(B\) 集合也是符合性质 \(5\) 的。

    如果 \(j>1\),那么指数范围在 \([0,j-1]\) 内的数的和的最大值应该为 \(-2^0+2(-2^1-2^2-\cdots -2^{j-1})=-2^{j+1}+3\),发现这个数永远小于 \(-2^j\),那么无论如何修改 \(2^j\) 前面的正负号都无法使得前缀和等于 \(1\),所以是不存在 \(j>1\) 的情况的。

于是我们证明了性质 \(5\) 是一个充要条件。

于是,我们只需要统计满足以上五个条件的集合数有多少种即可。

\(f_{i,j}\) 表示已经放了 \(i\) 个元素,并且这些元素的和为 \(1+jV\),其中 \(V\) 为现在要放的 \(2\) 的次幂是多少。

那么我们枚举选了 \(a\)\(V\),选了 \(b\)\(-V\),那么元素的和就变为了 \(1+(j+a-b)V\)。因为下一个填的数就要比现在的数乘 \(2\),所以 \(f_{i,j}\) 转移到 \(f_{i+a+b,\frac{j+a-b}{2}}\)

我们需要满足将这些数任意标正负号之后,和为 \(1\),那么我们只需要满足 \(j \equiv a+b \pmod 2\)\(a+b\ge \rvert j\lvert\) 即可。

然后考虑对这个系数集合分配原来的 \(x^i\),这个就是多重组合数的形式(\(\frac{n!}{\prod a!}\)),我们可以把 \(\frac{1}{\prod a!}\) 的部分拆到转移上,也就是 \(f_{i,j} \times \frac{1}{a!b!}\rightarrow f_{i+a+b,\frac{j+a-b}{2}}\)

初始状态为 \(f_{0,-1}=1\),答案为 \(n!f_{n,0}\)

需要注意,为了满足性质 \(4\),需要特殊判断一下,当 \(i=0\) 时,必须满足 \(a+b=1\) 时才可以转移。

Code

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 55, P = 1000000007;
int f[MAXN][MAXN << 1];
int n;
int fac[MAXN], inv[MAXN];
int qpow(int a, int b) {
    int ans = 1;
    while (b) {
        if (b & 1) ans = 1ll * ans * a % P;
        a = 1ll * a * a % P;
        b >>= 1;
    }
    return ans;
}
int main() {
    scanf("%d", &n);
    f[0][MAXN + -1] = 1;
    fac[0] = 1;
    for (int i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % P;
    inv[n] = qpow(fac[n], P - 2);
    for (int i = n; i >= 1; i--) inv[i - 1] = 1ll * inv[i] * i % P;
    for (int i = 0; i < n; i++) {
        for (int j = -n; j <= n; j++) if (f[i][j + MAXN]) {
            for (int a = 0; a <= n; a++) {
                for (int b = 0; i + a + b <= n; b++) if ((a || b) && ((j + a + b) % 2 == 0) && a + b >= abs(j)) {
                    if (i == 0 && a + b != 1) continue;
                    f[i + a + b][(j + a - b) / 2 + MAXN] = (f[i + a + b][(j + a - b) / 2 + MAXN]
                         + 1ll * f[i][j + MAXN] * inv[a] % P * inv[b]) % P;
                }
            }
        }
    }
    printf("%lld\n", 1ll * f[n][MAXN + 0] * fac[n] % P);
    return 0;
}
posted @ 2022-09-10 19:02  APJifengc  阅读(72)  评论(0编辑  收藏  举报