Loading

2.27 CW 模拟赛 赛时记录

前言

跟着策略做题, 注意数据检验, 不贪跟策略
一定要冷静, 放下, 耐心

看题

\(\rm{T1}\)

给我打上麻将了, 真的假的

  • 定义合法情况, 要求输出一组合法情况 / 合法情况的最值问题 / 求方案数
    • 模拟操作情况, 找到最好开销, 注意最大和最小
    • 列出合法情况需要满足的表达式, 在原序列中贪心选择最优情况
    • 构造: 先推性质, 不行打表
    • 找到不合法情况在什么时候出现, 通过对不合法情况的转化构造最优的合法情况
    • 往往利用 \(\rm{dp}\) , 结合约束处理当前方案
    • 先找到一组合法解, 然后在基础上进行调整
    • 一般优先考虑枚举性质更多, 或者确定性更强的元素

\(\rm{T2}\)

打表找规律, 可能有用

  • 定义合法情况, 要求输出一组合法情况 / 合法情况的最值问题 / 求方案数
    • 模拟操作情况, 找到最好开销, 注意最大和最小
    • 列出合法情况需要满足的表达式, 在原序列中贪心选择最优情况
    • 构造: 先推性质, 不行打表
    • 找到不合法情况在什么时候出现, 通过对不合法情况的转化构造最优的合法情况
    • 往往利用 \(\rm{dp}\) , 结合约束处理当前方案
    • 先找到一组合法解, 然后在基础上进行调整
    • 一般优先考虑枚举性质更多, 或者确定性更强的元素
  • 定义约束, 要求满足约束的情况数
    • 逆向思维
    • 考虑不满足约束的情况, 数学表示约束

\(\rm{T3}\)

  • 交换相邻元素性质
    • \(a\)\(b\) , 交换 \(|a - b|\)
    • 对于两个串的定位问题, 每个元素定位的花费就是关于其的逆序对个数
      证明: 从大权值到小权值, 逐个固定位置
    • 往往用固定之前的部分, 移动当前的部分来解决
  • 定义合法情况, 要求输出一组合法情况 / 合法情况的最值问题 / 求方案数
    • 模拟操作情况, 找到最好开销, 注意最大和最小
    • 列出合法情况需要满足的表达式, 在原序列中贪心选择最优情况
    • 构造: 先推性质, 不行打表
    • 找到不合法情况在什么时候出现, 通过对不合法情况的转化构造最优的合法情况
    • 往往利用 \(\rm{dp}\) , 结合约束处理当前方案
    • 先找到一组合法解, 然后在基础上进行调整
    • 一般优先考虑枚举性质更多, 或者确定性更强的元素

\(\rm{T4}\)

不好, 最后一题放期望
那只能看看了


跟着策略走, 不贪, 数据检验

\(\rm{T1}\)

思路

题意

给定 nn 个在 [1,m][1, m] 范围内的序列 aa
要求把序列 aa 划分成形如 {x,x,x} or {x,x+1,x+2}\{x, x, x\} \textrm{ or } \{x, x + 1, x + 2\} 这样的段的方案数

题目显然提示先搞出桶, 所以记 \(p_i\) 表示数字 \(i\) 出现的次数
\(f_i\) 表示用完前 \(i - 1\) 个数字, 没有使用过 \(i\) 的方案数

转移只有两种

  • \((1)\) 对于 \(i, i + 1, i + 2\) , 各自使用
  • \((2)\) 对于 \(i, i + 1, i + 2\) , 一个数字单独使用 \(3\)

每次转移, 如果只是单独使用 \(i\) , 那么转移到 \(f_{i + 1}\)
否则转移到 \(i + 3\)
现在的问题是, 对于 \((1), (2)\) 两种转移, 如何完全枚举所有情况

不妨枚举使用 \((1)\) 转移的次数, 记为 \(j\)
那么如果 \(p_i \gets p_i - j, p_{i + 1} \gets p_{i + 1} - j, p_{i + 2} \gets p_{i + 2} - j\) 之后, \(p_i, p_{i + 1}, p_{i + 2}\) 都是 \(3\) 的倍数, 那么可以转移
总复杂度是 \(\mathcal{O} (nm)\) 左右

不想数据检验, 先打一个试一下
挂的有点多, 先手搞一下
容易发现没考虑好

感觉可以用区间 \(\rm{dp}\) 的思路去搞

\(f_{[L, R]}\) 表示清空值域区间 \([L, R]\) 的方案数
初始化先预处理长度为 \(1, 3\) 的区间

然后不难列出式子

\[\begin{align*} f_{[L, R]} \gets \begin{cases} f_{[L, R - 1]}, \textrm{ case } p_{R - 1} \textrm{ mod } 3 = 0 \\ f_{[L + 1, R]}, \textrm{ case } p_{L + 1} \textrm{ mod } 3 = 0 \\ f_{[L + 3, R]}, 同上判断 \\ f_{[L, R - 3]}, 同上判断 \\ \end{cases} \end{align*}\]

只需要注意判断区间的合法性, 预处理长度为 \(3\) 的区间构造方法数即可

有点担心这个的正确性, 先看下后面有没有更好拿的分
发现还是假的, 不妨修改定义

\(f\) 表示用 \((1)\) 操作把剩下的都变成 \(3\) 倍数的方案数

实现

框架

预处理

  • \(f\) 数组
  • 任意连续三个区间中, 有多少种情况把他们清零 \((\)必须使用至少一次 \((1)\) 操作\()\)

转移

\(\rm{T2}\)

思路

题意

构造字典序最小的排列, 使得对于 1in1 \leq i \leq n , 都有 piik|p_i - i| \geq k

对于位置 \(i\) , \(p_i \in [i + k, n] \cup [1, i - k]\)
不太会, 不浪费时间了, \(n n!\) 打了跑, 记得观察一下规律万一瞪出来了呢

真瞪出来了

代码
#include <bits/stdc++.h>
const int MAXN = 20;

int n, k;
int bas[MAXN], ans[MAXN];

int res[300020];

int main() {
    scanf("%d %d", &n, &k);
    if (n <= 10) {
        memset(ans, 0x3f, sizeof ans);
        for (int i = 1; i <= n; i++) bas[i] = i;
        do {
            bool flag = true;
            for (int i = 1; i <= n; i++) {
                if (std::abs(bas[i] - i) < k) { flag = false; break; }
            }
            if (flag) {
                bool better = false;
                for (int i = 1; i <= n; i++) {
                    if (ans[i] != bas[i]) {
                        if (ans[i] > bas[i]) better = true;
                        break;
                    }
                }
                if (better) {
                    for (int i = 1; i <= n; i++) ans[i] = bas[i];
                }
            }
        } while (std::next_permutation(bas + 1, bas + n + 1));

        if (ans[1] > n) printf("-1");
        else for (int i = 1; i <= n; i++) printf("%d ", ans[i]);
    } else {
        int len = 2 * k;
        for (int i = 1; i <= n; i += len) {
            for (int j = i + k; j <= i + len - 1; j++) res[j - k] = j;
            for (int j = i; j <= i + k - 1; j++) res[j + k] = j;
        }
        for (int i = 1; i <= n; i++) printf("%d ", res[i]);
    }

    return 0;
}

\(\rm{T3}\)

你想知道的还挺多

思路

题意

给定 pi,wip_i, w_i
一次交换定义为对 pi,pi+1p_{i} , p_{i + 1} 的操作
要求 wpi+wpi+1Ww_{p_i} + w_{p_{i + 1}} \leq W , 满足要求可以 swap{pi,pi+1}\textrm{swap}\{p_i, p_{i + 1}\}

但是这题拿个 \(30\) 真的差不多了, 遂决定直接干个大聪明暴力

代码
#include <bits/stdc++.h>
const int MAXN = 11;

int n, W;
int w[MAXN];
std::vector<int> p;

std::map<std::vector<int>, bool> exists;
std::map<int, std::vector<int>> num; int cnt = 0;

void dfs(int now) {
    for (int i = 2; i <= n; i++) {
        if (w[num[now][i]] + w[num[now][i - 1]] <= W) {
            std::vector<int> nxt = num[now];
            std::swap(nxt[i], nxt[i - 1]);
            if (exists[nxt]) continue;
            else {
                exists[nxt] = true, num[++cnt] = nxt;
                dfs(cnt);
            }
        }
    }
}

int main() {
    scanf("%d %d", &n, &W);
    p.resize(11);
    for (int i = 1; i <= n; i++) scanf("%d", &p[i]);
    for (int i = 1; i <= n; i++) scanf("%d", &w[i]);

    exists[p] = true; num[++cnt] = p;
    dfs(cnt);

    std::vector<int> res; res.resize(11);
    for (int i = 1; i <= n; i++) res[i] = 0x3f3f3f3f;
    int ans = 0;
    for (int i = 1; i <= cnt; i++) {
        bool better = false;
        for (int j = 1; j <= n; j++) {
            if (res[j] != num[i][j]) {
                if (res[j] > num[i][j]) better = true;
                break;
            }
        }
        if (better) {
            for (int j = 1; j <= n; j++) res[j] = num[i][j];
        }
    }
    printf("%d\n", cnt);
    for (int j = 1; j <= n; j++) printf("%d ", res[j]);

    return 0;
}

曹了呀, 大样例飞不过去, 怎么办怎么办怎么办
不管了, 急急急急急急, 直接去 \(\rm{T4}\)
\(\textrm{cnm}\) , \(\textrm{vscode}\) 配置问题

\(\rm{T4}\)

思路

直接模拟

对于一种状态 \(\mathbb{S}\) , 考虑计算需要多少钱
贪心的在他检票的时候, 如果当前没有票, 就买一张

不管了, 这题暴力先给我丢了, 先去冲 \(\rm{T1}\)

posted @ 2025-02-27 15:42  Yorg  阅读(13)  评论(0)    收藏  举报