[AHOI2022] 山河重整

[AHOI2022] 山河重整

考虑集合 \(S \subseteq \{1,2,\dots,n\}\) 合法的充要条件。

根据构造合法集合 \(S\) 的过程,不难给出如下断言:

  • 对于 \(i \in [1,n]\),满足 \(\sum\limits_{x \in S \land x \leq i} x \geq i\)

考虑容斥原理,我们假设不满足条件的 \(i\) 中最小值为 \(i_0\),并在 \(i_0\) 处统计。

考虑这意味着什么。首先 \(i_0\) 不能被选中,否则 \(i_0\) 就会满足条件。

其次既然 \(i_0\) 已经不合法了,此时 \(> i_0\) 的数是可以任意选择的。

考虑 \(\leq i_0 - 1\) 的数合法,意味着 \(S\) 内 \(\leq i_0 - 1\) 的数和 \(\geq i_0 - 1\)。

然而 \(i_0\) 不能被选且 \(\leq i_0\) 的数和 \(< i_0\),故得知以下结论:

  • \(\sum\limits_{x \in S \land x \leq i_0 - 1} x = i_0 - 1\)

我们记 \(f_i\) 表示满足以下两条性质的集合 \(S \in \{1,2,\dots,n\}\) 个数:

  • \(\sum\limits_{x \in S} x = i\)

  • 对于 \(j \in [1,i]\),满足 \(\sum\limits_{x \in S \land x \leq j} x \geq j\)

这样的问题仍然较难计算,考虑再次容斥。

我们记 \(g_i\) 表示满足以下性质的集合 \(S \subseteq \{1,2,\dots,n\}\) 个数:

  • \(\sum\limits_{x \in S} x = i\)

考虑 \(g_i\) 如何计算,这类似于完全背包。

具体地,我们声称 \(|S| \leq O(\sqrt{n})\),容易证明这是显然的。

接下来画出类似柱状图的东西,比如选择了 \(\{1,3,4,6\}\) 就画出下图:

image

容易发现我们可以将柱状图横过来看。

显然此时的柱状图高度 \(h\) 一定满足 \(1 + 2 + \dots + h \leq n\),先找到最大的 \(h\)。

我们有 \(h = O(\sqrt{n})\),并且 \(1\) 到 \(h\) 每个数至少被加一次。

显然这可以用类似完全背包的形式转移,使用滚动数组优化空间到 \(O(n)\)。

显然,横向和纵向的方案形成了对应关系。

每次转移后,我们尝试更新答案,复杂度是 \(O(n \sqrt{n})\)。

接下来考虑用 \(g_i\) 算出 \(f_i\),仍然考虑容斥。

我们枚举最小的 \(j < i\) 使得 \(\sum\limits_{x \in S \land x \leq j} x < j\)。

显然 \(\sum\limits_{x \in S \land x \leq j-1} x = j-1\),且此时 \(j\) 必然不选。

同时有 \(\sum\limits_{x \in S \land j+1 \leq x \leq i} x = i - j + 1\)。

考虑前者的贡献,显然为 \(f_{j-1}\)。

接下来我们考虑后者的贡献,一个显然的事实是 \(i - j + 1 > 0\)。

也就是说我们至少会选择一个数,故左侧式 \(\geq j+1\)。

所以 \(i - j + 1 \geq j + 1\),得 \(2j \leq i\)。

考虑先求出 \(\lfloor \dfrac{n}{2} \rfloor\) 以内的所有 \(f_i\)。

接下来 \(\lfloor \dfrac{n}{2} \rfloor + 1\) 以上的 \(f_i\) 只可能由前面的部分转移。

考虑枚举拆分出数的个数,记作 \(k\)。

我们将这 \(k\) 个数每个减去 \(j\),最后的和变为 \(i-(k+1)j+1\),乘上对应的 \(f\) 即可。

时间复杂度 \(O(n \sqrt{n})\)。

代码一股 AI 味是因为让 AI 卡常。

#include<iostream>
#include<cstdio>
using namespace std;

long long mod;
long long f[500010], g[500010], dp[500010], pow_2[500010];
int max_cnt[500010];

void calc(int n) {
    if (n == 1) {
        f[0] = f[1] = 1;
        return;
    }
    
    const long long MOD = mod;
    int new_n = n >> 1;  // 用位运算代替除法
    calc(new_n);
    
    // 复制数组
    for (int i = new_n + 1; i <= n; ++i) {
        f[i] = g[i];
    }
    
    // 清零
    for (int i = 0; i <= n; ++i) {
        dp[i] = 0;
    }
    
    int max_i = max_cnt[n];
    for (int i = max_i; i >= 1; --i) {
        int step = i;
        // 移位
        for (int j = n; j >= step; --j) {
            dp[j] = dp[j - step];
        }
        
        // 内层循环优化
        int limit = n;
        for (int j = 1; (step + 1) * j + step - 1 <= limit; ++j) {
            int idx = (step + 1) * j + step - 1;
            dp[idx] += f[j - 1];
            if (dp[idx] >= MOD) dp[idx] -= MOD;
        }
        
        // 累加
        for (int j = step; j <= n; ++j) {
            dp[j] += dp[j - step];
            if (dp[j] >= MOD) dp[j] -= MOD;
        }
    }
    
    for (int i = new_n + 1; i <= n; ++i) {
        f[i] -= dp[i];
        if (f[i] < 0) f[i] += MOD;
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n;
    cin >> n >> mod;
    
    const long long MOD = mod;
    int pre_num = 0, pre_sum = 0;
    pow_2[0] = 1;
    
    for (int i = 1; i <= n; ++i) {
        pow_2[i] = (pow_2[i - 1] << 1) % MOD;  // 用移位代替乘法
        while (pre_sum + pre_num + 1 <= i) {
            ++pre_num;
            pre_sum += pre_num;
        }
        max_cnt[i] = pre_num;
    }
    
    // 初始化 dp 和 g
    for (int i = 0; i <= n; ++i) dp[i] = 0;
    
    int max_i = max_cnt[n];
    for (int i = max_i; i >= 1; --i) {
        int step = i;
        for (int j = n; j >= step; --j) {
            dp[j] = dp[j - step];
        }
        dp[step] = (dp[step] + 1) % MOD;
        for (int j = step; j <= n; ++j) {
            dp[j] += dp[j - step];
            if (dp[j] >= MOD) dp[j] -= MOD;
        }
    }
    
    dp[0] = 1;
    for (int i = 0; i <= n; ++i) {
        g[i] = dp[i];
    }
    
    calc(n);
    
    long long ans = pow_2[n];
    for (int i = 1; i <= n; ++i) {
        ans -= f[i - 1] * pow_2[n - i] % MOD;
        if (ans < 0) ans += MOD;
    }
    
    cout << ans << '\n';
    return 0;
}
posted @ 2026-03-27 16:28  Oken喵~  阅读(24)  评论(0)    收藏  举报