Loading

2.18 CW 模拟赛 赛时记录

前言

考一场古老 \(\rm{NOIP}\) , 甚至是 \(3\)

策略定好正常考即可, 别想太多

看题

\(\rm{T1}\)

  • 定义操作 (约束) 和开销 / 收益, 要求最值化开销 / 收益
    • 推式子计算约束条件
    • 模拟操作情况, 找到最好开销, 注意最大和最小, 一般来说可以贪贪心 (将简单情况先处理, 然后在基础上处理最值)
    • 考虑操作对答案的影响 (推式子) , 据此对操作进行排序
    • 往往利用 \(\rm{dp}\) , 结合约束处理当前方案
    • 列出合法情况需要满足的表达式, 在原序列中贪心选择最优情况

\(\rm{T2}\)

啊?

\(\rm{T3}\)

不好是概率, 数论大佬要发力了, 那我怎么办


总的来说, 今天每道题先给 \(30 \sim 40 \textrm{ min}\) , 然后有部分分可以给 \(20 \textrm{ min}\) 打, 绝对不能贪
然后对数据一定要有检验, 不行多打暴力

争取做到无遗憾就行

\(\rm{T1}\)

思路

题意

定义一个好的序列为: 不存在相邻两个数都是奇数/偶数

希望对于一个长度为 nn 的序列, 可以重新排列, 将其变为一个好的序列
定义一种重排方式的花费是重排前后每个数字位置差的绝对值之和
希望最小化花费, 输出字典序最小的一组结果

最初输入的 \(a\) 可以看做是一个 \(01\) 串, 要求不能有连续的 \(0/1\)

所以不难确定最终的形式, \(n\) 为偶数时可能有两种形式
问题转化为怎么填数花费最小, 字典序最小

猜测奇偶性相同的数中按照顺序分配花费最小, 因为不这样一定不优
但是怎么让字典序最小?

先想什么情况可以让操作次数不变, 但是交换序列中的数
打个代码验证一下想法

会了一点, 就是差不多的, 赶快先开后面的, 这个再想

现在遇到的问题

  • 如何交换最优
  • 如何处理重数

到时候冲一下 \(n = 5000\) 差不多了


好的, 还剩下 \(30 \textrm{ min}\) 过来磕一下

代码

#include <bits/stdc++.h>
#define int long long
const int MAXN = 5206;

int n;
int C, CC;
int a[MAXN], tmp;
int b[MAXN], c[MAXN];
std::vector<int> split[2]; int pos[2][MAXN];
int cpos[2][MAXN];

signed main()
{
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++) scanf("%lld", &a[i]), tmp += (a[i] % 2), split[a[i] % 2].push_back(a[i]), pos[a[i] % 2][split[a[i] % 2].size() - 1] = i; // 重数问题
    if (n <= 200) CC = 500;
    else if (n <= 2000) CC = 20;
    else if (n <= 5000) CC = 5;
    C = CC;

    /*本 if 中需要添加一些取优解*/
    if (n % 2 == 0) {
        /*先奇数情况*/
        for (int i = 1; i <= n; i += 2) b[i] = split[1][i / 2];
        for (int i = 2; i <= n; i += 2) b[i] = split[0][(i - 1) / 2];

        for (int i = 0; i <= n; i++) cpos[0][i] = pos[0][i], cpos[1][i] = pos[1][i];

        while (C--) {
            for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
                if (b[i] < b[j] || (b[i] % 2) != (b[j] % 2)) continue;
                if (std::max(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]) <= i || j <= std::min(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2])) std::swap(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]), std::swap(b[i], b[j]);
            }
        }

        for (int i = 0; i <= n; i++) pos[0][i] = cpos[0][i], pos[1][i] = cpos[1][i];
        C = CC;
        /*先偶数情况*/
        for (int i = 1; i <= n; i += 2) c[i] = split[0][i / 2];
        for (int i = 2; i <= n; i += 2) c[i] = split[1][(i - 1) / 2];
        while (C--) {
            for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
                if (c[i] < c[j] || (c[i] % 2) != (c[j] % 2)) continue;
                if (std::max(pos[c[i] % 2][(i - 1) / 2], pos[c[j] % 2][(j - 1) / 2]) <= i || j <= std::min(pos[c[i] % 2][(i - 1) / 2], pos[c[j] % 2][(j - 1) / 2])) std::swap(pos[c[i] % 2][(i - 1) / 2], pos[c[j] % 2][(j - 1) / 2]), std::swap(c[i], c[j]);
            }
        }
        /*取优解*/
        bool tag = 0; //0 b; 1 c
        for (int i = 1; i <= n; i++) {
            if (b[i] != c[i]) {
                if (b[i] > c[i]) tag = 1;
                else tag = 0;
                break;
            }
        }
        if (tag) {
            for (int i = 1; i <= n; i++) printf("%lld ", c[i]);
        } else {
            for (int i = 1; i <= n; i++) printf("%lld ", b[i]);
        }
    } else {
        /*先奇数情况*/
        if (tmp > n - tmp) {
            for (int i = 1; i <= n; i += 2) b[i] = split[1][i / 2];
            for (int i = 2; i <= n; i += 2) b[i] = split[0][(i - 1) / 2];
            while (C--) {
                for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
                    if (b[i] < b[j] || (b[i] % 2) != (b[j] % 2)) continue;
                    if (std::max(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]) <= i || j <= std::min(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2])) std::swap(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]), std::swap(b[i], b[j]);
                }
            }
        } else {
            for (int i = 1; i <= n; i += 2) b[i] = split[0][i / 2];
            for (int i = 2; i <= n; i += 2) b[i] = split[1][(i - 1) / 2];
            while (C--) {
                for (int i = 1; i <= n; i++) for (int j = i + 1; j <= n; j++) {
                    if (b[i] < b[j] || (b[i] % 2) != (b[j] % 2)) continue;
                    if (std::max(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]) <= i || j <= std::min(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2])) std::swap(pos[b[i] % 2][(i - 1) / 2], pos[b[j] % 2][(j - 1) / 2]), std::swap(b[i], b[j]);
                }
            }
        }
        for (int i = 1; i <= n; i++) printf("%lld ", b[i]);
    }

    return 0;
}

\(\rm{T2}\)

后面的题真就有多少拿多少了, 不要留遗憾

思路

题意 每次操作一种颜色使其翻转, 求极长亮灯区间个数

\(\mathcal{O} (nq)\) 是送的
再拼一点可以开始打了

对于 \(k \leq 500\) 的情况, 不难发现相邻的同颜色灯泡没有用, 但是好像还是不好
啊啊啊, 根号分治是什么啊

不难发现我们可以处理出两种颜色中间有几条连边, 然后动态维护边的有效性即可快速做出子任务 \(1 \sim 3\)
根号分治没看出怎么搞, 先丢了

代码

#include <bits/stdc++.h>
const int MAXN = 2e5 + 20;
const int MAXK = 520;

int n, q, k;
int col[MAXN];

class subtask12
{
private:
    int op[MAXN];

public:
    void solve() {
        memset(op, 0, sizeof op);
        while (q--) {
            int x; scanf("%d", &x);
            for (int i = 1; i <= n; i++) if (col[i] == x) op[i] = !op[i];
            int ans = 0; for (int i = 1; i <= n; i++) if (op[i] == 1 && op[i - 1] == 0) ans++;
            printf("%d\n", ans);
        }
    }
} sub12;

class subtask3
{
private:
    int edge[MAXK][MAXK], num[MAXK];
    int ans = 0;
    int op[MAXK];

public:
    void solve() {
        memset(edge, 0, sizeof edge); memset(op, 0, sizeof op); memset(num, 0, sizeof num);
        for (int i = 1; i <= n; i++) if (col[i] != col[i - 1]) num[col[i]]++, edge[col[i]][col[i - 1]]++, edge[col[i - 1]][col[i]]++;
        while (q--) {
            int x; scanf("%d", &x); op[x] = !op[x];
            if (op[x]) {
                ans += num[x];
                for (int i = 1; i <= k; i++) {
                    if (!op[i] || i == x) continue;
                    ans -= edge[x][i];
                }
            } else {
                ans -= num[x];
                for (int i = 1; i <= k; i++) {
                    if (!op[i] || i == x) continue;
                    ans += edge[x][i];
                }
            }
            
            printf("%d\n", ans);
        }
    }
} sub3;

int main()
{
    scanf("%d %d %d", &n, &q, &k);
    for (int i = 1; i <= n; i++) scanf("%d", &col[i]);

    if (n <= 5000 & q <= 5000) sub12.solve();
    else if (k <= 500) sub3.solve();
    else sub3.solve();

    return 0;
}

\(\rm{T3}\)

骗骗骗

思路

\(\mathcal{O} (2^n)\) 似乎是送的
那先拿到手上

代码

#include <bits/stdc++.h>
#define int long long
const int MOD = 998244353;
const int MAXN = 22;

inline int qpow(int a, int b, int p) {
    int res = 1, base = a;
    while (b) {
        if (b & 1) res = res * base % p;
        base = base * base % p;
        b >>= 1;
    }
    return res;
}

namespace calc {
    int inv(int a) { return qpow(a, MOD - 2, MOD); }
    int add(int a, int b) { return a + b > MOD ? a + b - MOD : a + b; }
    int dec(int a, int b) { return a - b < MOD ? a - b + MOD : a - b; }
    int mul(int a, int b) { return (a * b) % MOD; }
    int dvi(int a, int b) { return mul(a, inv(b)); }
    void addon(int &a, int b) { a = add(a, b); }
    void mulon(int &a, int b) { a = mul(a, b); }
} using namespace calc;

int n;
int lwin, bwin;
int bs[MAXN];
int ans[MAXN];
int pre[MAXN], suf[MAXN]; // 前后缀中, 不在集合里的数量
int bpow[MAXN], lpow[MAXN];

signed main()
{
    int a, b;
    scanf("%lld %lld %lld", &n, &a, &b);
    lwin = dvi(a, b), bwin = dvi(b - a, b);
    for (int i = 0; i <= n; i++) bpow[i] = qpow(bwin, i, MOD), lpow[i] = qpow(lwin, i, MOD);
    for (int i = 1; i <= n; i++) ans[i] = 0;
    bs[1] = 1; for (int i = 2; i <= n; i++) bs[i] = add(mul(bs[i - 1], bs[i - 1]), 2);

    for (int S = 0; S < (1 << n); S++) {
        int length = __builtin_popcount(S);
        if (length == 0 || length == n) continue;
        pre[0] = 0; suf[n + 1] = 0; for (int i = n; i >= 1; i--) suf[i] = suf[i + 1] + (!((S >> (i - 1)) & 1));

        int tmp = 1;
        for (int i = 1; i <= n; i++) {
            pre[i] = pre[i - 1] + (!((S >> (i - 1)) & 1));
            if ((!((S >> (i - 1)) & 1))) {
                continue;
            }

            mulon(tmp, mul(bpow[pre[i]], lpow[suf[i]]));
        }
        addon(ans[length], tmp);
    }
    int res = 0;
    for (int i = 1; i < n; i++) addon(res, mul(ans[i], bs[i]));
    printf("%lld", res);

    return 0;
}
posted @ 2025-02-18 16:15  Yorg  阅读(9)  评论(0)    收藏  举报