Loading

[CF 2166D] Marble Council

思路

肯定是在值域上处理, 类似今年 S T4 的将与未扫描部分相关的部分单独统计
定义 \(c_x\)\(a_i = x\) 的数量
考虑 \(f_{i, j, k}\) 表示考虑到数字 \(i\), 当前要求容量到 \(j\), 当前容量为 \(k\) 的方案数

\[ \begin{gather*} c_i \times f_{i, j, k} \to f_{i + 1, j, k + c_i} \\ f_{i, j, k} \to f_{i + 1, \max(j, c_i), k} \end{gather*} \]

不难发现这个东西复杂度好像不怎么是 \(O(n^3)\)
发现 \(j \leq \sqrt{n}\), 因为 \(j\) 只能取出现过的 \(c_i\)
发现 \(i \leq 2\sqrt{n}\), 因为 \(< \sqrt{n}\) 的只有 \(\sqrt{n}\) 种, \(> \sqrt{n}\) 的最多出现 \(\sqrt{n}\)
所以复杂度是 \(\mathcal{O} (n^2)\)

#include <bits/stdc++.h>
using namespace std;
#define debug(...) fprintf(stderr, __VA_ARGS__)

const int MOD = 998244353;

int main() {
    // ios::sync_with_stdio(false);
    // cin.tie(nullptr);
    
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        vector<int> a(n);
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }
        
        vector<int> cnt(n + 1, 0);
        for (int x : a) {
            cnt[x]++;
        }
        
        // 统计不同的出现次数并排序
        vector<int> values, freq;
        for (int i = 1; i <= n; i++) {
            if (cnt[i] > 0) {
                values.push_back(i);
            }
        }
        sort(values.begin(), values.end());
        values.erase(unique(values.begin(), values.end()), values.end());
        
        // 统计每种出现次数的频率
        // vector<int> freq(n + 1, 0);
        for (int i = 1; i <= n; i++) {
            if (cnt[i] > 0) {
                freq.push_back(cnt[i]);
            }
        }
        sort(freq.begin(), freq.end());
        freq.erase(unique(freq.begin(), freq.end()), freq.end());
        
        int m = values.size();
        int mm = freq.size();
        
        // dp[j_idx][k] - j_idx 是 values 中的索引,k 是当前容量
        vector<vector<int>> dp(mm + 1, vector<int>(n + 1, 0));
        // 初始状态:没有数字,要求容量为0,实际容量为0
        dp[0][0] = 1;

        int bababoy = 0;
        
        for (int val : values) {
            bababoy++;
            vector<vector<int>> new_dp(mm + 1, vector<int>(n + 1, 0));
            
            for (int j_idx = 0; j_idx <= mm; j_idx++) {
                int current_j = (j_idx == 0) ? 0 : freq[j_idx - 1];
                for (int k = 0; k <= n; k++) {
                    if (dp[j_idx][k] == 0) continue;
                    
                    // 转移1: 将当前数字作为众数
                    if (k + cnt[val] <= n) {
                        new_dp[j_idx][k + cnt[val]] = (new_dp[j_idx][k + cnt[val]] + 
                                                 1LL * dp[j_idx][k] * cnt[val]) % MOD;
                    }
                    
                    // 转移2: 不将当前数字作为众数
                    int new_j = max(current_j, cnt[val]);
                    // 找到 new_j 在 freq 中的索引
                    int new_j_idx = lower_bound(freq.begin(), freq.end(), new_j) - freq.begin() + 1;
                    new_dp[new_j_idx][k] = (new_dp[new_j_idx][k] + dp[j_idx][k]) % MOD;
                }
            }
            
            dp = move(new_dp);
            // /*debug 所有有值的 dp 位置*/
            // for (int j_idx = 0; j_idx <= mm; j_idx++) {
            //     int j_val = (j_idx == 0) ? 0 : values[j_idx - 1];
            //     for (int k = 0; k <= n; k++) {
            //         if (dp[j_idx][k] > 0) {
            //             debug("dp[%d][%d][%d] = %d\n", bababoy, j_idx, k, dp[j_idx][k]);
            //         }
            //     }
            // }
        }

        
        // 统计所有满足 j <= k 的状态
        int ans = 0;
        for (int j_idx = 0; j_idx <= mm; j_idx++) {
            int j_val = (j_idx == 0) ? 0 : freq[j_idx - 1];
            for (int k = 0; k <= n; k++) {
                if (j_val <= k) {
                    ans = (ans + dp[j_idx][k]) % MOD;
                }
            }
        }
        
        cout << ans << '\n';
    }
    
    return 0;
}

总结

dp 常用规模法理解

值域上处理和填一个坑的思想

posted @ 2025-11-17 21:56  Yorg  阅读(86)  评论(0)    收藏  举报