Loading

3.13 CW 模拟赛 T1. 打表

前言

先补这个题可能是因为挂的最多
目标是补到 \(50 / 70\) , 这个差不多把
感觉理解 \(70\) 之后应该可以顺便把 \(100\) 写了?

思路

首先是赛时搞出来的性质:

后面的序列可以表示为之前的某个序列加上一个质数, 我们只需要依次枚举之前长度为 \(0, 1, 2, \cdots\) 的序列, 然后直接在末尾加数使其等于当前枚举的和, 一定符合性质, 剪枝方法就是只要不合法就剪掉

首先明确上面的两个东西都错完了
性质相当于对于之前出现的两个长度相同的串 \(t_1, t_2\) , 假设 \(t_1, t_2\) 的末尾值分别为 \(k_1, k_2\) , 和分别为 \(s_1, s_2\) , 那么必定满足若有两个质数 \(p_1, p_2 \textrm{ s.t. } s_1 + p_1 = s_2 + p_2, p_1 \geq k_1, p_2 \geq k_2\) , 则在 \(t_1\) 后插入 \(p_1\) , \(t_2\) 后插入 \(p_2\)\({t'}_1, {t'}_2\) 必定满足顺序

其实不正确性很明显, 但是小数据没能卡掉这一点, 比较可惜, 也不能说我没有数据检验, 我也不太可能赛时发现这个错误, 因为确实是一个很简单且易于利用的性质

因为如果 \(s_1, s_2\) 不等, 对 \(t_1, t_2\) 字典序的约束也就不一定成立, 那么后面这个自然不成立


反正这个真的没救, \(\rm{rp}\) 就是用在这种地方的

首先考虑枚举序列和, 在其中枚举一下序列长度, 然后按字典序 \(\rm{dfs}\) 即可
这种有单调性的序列, 一般可以写出复杂度优秀的搜索算法, 这里先给出 \(\rm{donaldqian}\) 大佬的代码供我自己研究

30pts 代码
void dfs (int need, int mxstep, int step, int las, string pre) {
    if (step == mxstep + 1) {
        if (need) return ;
        cout << '{' << pre << '}' << ',';
        len += 3 + pre.size (); return ;
    }
    if (step > 1) pre += ',';
    for (int i = las; i <= cntp; i++) {
        if (need < pri[i]) break;
        dfs (need - pri[i], mxstep, step + 1, i, pre + p[i]);
        if (len > 1e5) break;
    }
}

for (int i = 2; i <= 1000; i++) {
    if (isprime (i)) pri[cntp] = i, printf("{%d},", i), len += 3;
    if (len >= 1e5) break;
    for (int j = 2; j <= i / 2; j++) {
        dfs (i, j, 1, 1, "");
    }
}

对于其中和一定, 长度一定的串, 不难发现只有唯一方法搜索到他
举个例子, 序列 {2, 2, 3, 3, 3, 7} 一定只能从下面的方式得来
{} -> {2} -> {2, 2} -> {2, 2, 3} -> {2, 2, 3, 3} -> {2, 2, 3, 3, 3} -> {2, 2, 3, 3, 3, 7}
而路径上的每一步也只能在这个路径上得出

然后剪一下, 复杂度感觉也很好啊, 大概过掉 \(50\) 没啥毛病


考虑 \(70\) 怎么做?
首先必然不能从 \(1\) 开始了, 观察性质

如果我们能知道开始处的序列和/序列长度, 就能在正确的地方开始做, 从而简化问题
\(f_{i, j, k}\) 表示从第 \(i\) 个素数开始构成的,序列和为 \(j\), 长度为 \(k\) 的素数导出序列个数
\(g_{i, j, k}\) 表示从第 \(i\) 个素数开始构成的,序列和为 \(j\), 长度为 \(k\) 的素数导出序列对应字符串的总长度

这就是一个简单的在质数集合上做的背包, 复杂度就是状态数
看下代码

代码
f[top + 1][0][0] = 1;
for (int i = top; i; --i) {
    int x = pr[i];
    for (int t = 0; x * t <= lim; ++t) std::copy(f[i + 1][t], f[i + 1][t] + lim + 1, f[i][t]);
    for (int t = 0; x * t <= lim; ++t) std::copy(g[i + 1][t], g[i + 1][t] + lim + 1, g[i][t]);
    for (int t = 0; t * x <= lim; ++t) {
        for (int j = x; j <= lim; ++j) {
            f[i][t + 1][j] += f[i][t][j - x];
            g[i][t + 1][j] += g[i][t][j - x] + rcnt[i] * f[i][t][j - x];
        }
    }
}

不难发现因为状态中自带了 这个信息, 可以做到每个状态被常数次数更新

然后跳跃到应该在的地方, 就可以进行搜索了
这个东西非常难写, 我真不想管了

总结

具有单调性的序列往往可以直接搜索
对一堆生成序列进行排序的问题, 往往转化成确定条件生成序列的问题

这种不能从起点开始处理的问题, 往往考虑先跳跃到询问点, 然后在暴力处理
有些时候, \(\rm{dp}\) 维数够多是可以同维度自己转移到自己的, 一种特殊的东西吧

posted @ 2025-03-13 20:33  Yorg  阅读(19)  评论(0)    收藏  举报