Loading

1.3 CW 模拟赛 赛时记录

前言

还是传统手法, 冲冲冲

看题

\(\rm{T1}\)

好吧, 不会高精度, 寄了

\(\rm{T2}\)

博弈

\(\rm{T3}\)

我不到啊

\(\rm{T4}\)

看不懂思密达


首先这套题肯定不是能拿大分的题, 所以今天尽量多分析, 给每个题留足思考时间, 然后多打暴力, 最后拼高分 / 正解

以后也尽量做到看题的时候奠定策略基础

\(\rm{T1}\)

思路

目标是拿到 \(50 \%\) , 后面的都需要高精度, 先不管他

首先你肯定是要观察性质的, 你容易发现, 当两个数的数字分配完成时, 那么数字就随之确定, 一定是从大到小排序

所以 \(50 \%\) 好像直接爆搜就能过?
不过一般不会是这样的, 分析一下复杂度, 你发现总共的分配可能是 \(2^{A + B}\) 级别的
好吧就是爆搜能过

实现

框架

首先枚举每个数分配方式, 判断是否合法之后统计答案

代码

好像时间给的很长

考场上忘了快写怎么打是真的绷不住, 现造了一个

#include <bits/stdc++.h>
// #define test_case
#define int long long
const int MAXVAL = 10;
const int MAXN = 21;

void write(__int128 x) {
    if (x / 10 == 0) { std::cout << (char)('0' + x); return; }
    write(x / 10);
    std::cout << (char)('0' + x - ((x / 10) * 10));
}

int T;
int a, b;
int num[MAXVAL];
int ret[MAXN];

bool cmp(int a, int b) { return a > b; }

__int128 ans = 0;
__int128 calc(int s) {
    std::vector<int> A, B; int pos = 0; A.clear(), B.clear();
    while (s) {
        if (s & 1) A.push_back(ret[pos]); else B.push_back(ret[pos]);
        s >>= 1, pos++;
    }

    std::sort(A.begin(), A.end(), cmp) , std::sort(B.begin(), B.end(), cmp);
    __int128 Anum = 0, Bnum = 0;
    for (int i = 0; i < A.size(); i++) Anum = Anum * 10 + A[i]; for (int i = 0; i < B.size(); i++) Bnum = Bnum * 10 + B[i];
    return Anum * Bnum;
}

signed main()
{
    #ifdef test_case
        freopen(".in", "r", stdin);
        freopen(".out", "w", stdout);
    #endif

    scanf("%lld", &T);
    while (T--) {
        ans = 0;
        scanf("%lld %lld", &a, &b);
        for (int i = 1; i <= 9; i++) scanf("%lld", &num[i]);
        for (int i = 0, j = 1; i < a + b; i++) {
            if (num[j]) ret[i] = j, num[j]--;
            else { j++; ret[i] = j, num[j]--; }
        }

        for (int s = 0; s < (1 << (a + b)); s++) { // 枚举状态 , 1 为分给 a , 2 为分给 b
            if (__builtin_popcount(s) != a) continue;
            ans = std::max(ans, calc(s));
        }
        write(ans); puts("");
    }

    return 0;
}

极限数据 \(780 \ \rm{ms}\) , 应该能过

捣鼓了一下正确性浪费了一点时间, 但是应该没问题了

\(\rm{T2}\)

目前得分 \(50 \ \rm{pts}\)

思路

还是继续拼暴力分, 最后再去搞正解
还是神秘博弈论, 先猜一个贪心(

手模一下, 看下有没有好的性质
考虑确定两个人起手的点之后, 能不能 \(\mathcal{O} (n)\) 的计算出最大答案?
容易发现的是, 两个人选择的范围相当于包含起始点的一段圆弧, 然后拼起来组成一个圆, 其中, 距离起始点更近的点, 更容易被选到, 这个是感性理解

具体怎么写还是考虑动态规划?
一个很好的性质是确定了一方的选点, 另一方的也就随之确定, 所以我们可以把博弈过程看做是确定一个分配方案

你发现确定两个人的起始点, 其实是可以确定 \(\rm{Alice}\) 的选点可能的, 考虑写个代码验证一下
具体的, 就是分成两个弧, \(\rm{Alice}\) 可以选择一个弧的优势(过半), 然后其他的劣势

感觉现在是猜结论, 全靠感性, 我也不知道怎么解释这个问题

那么这样枚举两个起始点, 好像是 \(\mathcal{O} (n^2)\) 的?

具体的

  • 两边都是奇数个 : 可以选择两种方式, 选取一边的超过一半和另外一边的少于一半
  • 两边都是偶数个 : 唯一情况分一半
  • 一奇一偶 : 唯一情况多吃一点

所以这样子打下来, 可以知道 \(\rm{Alice}\) 确定唯一占领时, \(\rm{Bob}\) 怎么选才能最大化自己, 然后就知道答案了

做到 \(\mathcal{O} (n^2)\) 唯一需要的是考虑环上的和怎么快速计算

实现

目前得分 \(95 \ \rm{pts}\) , 剩下全部冲 \(\rm{T2}\)

框架

首先是枚举两个分割点
如何把两边分开 : 断环为链拷两遍
如何计算和 : 前缀和
如何快速计算分一半 : 计算编号之差

我们细致一点方便写代码:
首先读入这个环的时候就将其断开复制两遍
枚举 \(\rm{Alice}\) 的起手点 ( \(\rm{subtask} \ 3\) 直接钦定为 \(1\) ), 对于这个点我们枚举 \(\rm{Bob}\) 的所有起手点, 然后我们可以找到这个起手点的两个位置, 显然的根据上面的分类讨论

  • 两边都是奇数个 : 可以选择两种方式, 选取一边的超过一半和另外一边的少于一半
  • 两边都是偶数个 : 唯一情况分一半
  • 一奇一偶 : 唯一情况多吃一点

还有一个问题 : 如何在链上找到两个起手点
其实很简单, 假设两个起手点 \(u < v\) , 直接找到 \(v\) 第二遍复制的位置即可

然后检查答案即可

注意同 \(\rm{Alice}\) 起手点之间取 \(\min\) , 不同之间取 \(\max\)

代码

#include <bits/stdc++.h>
// #define test_case
#define int long long
const int MAXN = 5e5 + 20;

int n;
int cir[MAXN << 1], dis[MAXN << 1];
bool sub3 = false; // 是否满足 sub3 的性质

signed main()
{
#ifdef test_case
    freopen("ex_game4.in", "r", stdin);
    freopen("myout.out", "w", stdout);
#endif

    scanf("%lld", &n); sub3 = (n > 5000);
    for (int i = 1; i <= n; i++) scanf("%lld", &cir[i]);
    for (int i = 1; i <= n; i++) cir[n + i] = cir[i]; // 断环为链
    for (int i = 1; i <= 2 * n; i++) dis[i] = dis[i - 1] + cir[i];

    if (sub3) {
        int u = 1;
            int res = 0x3f3f3f3f3f3f3f3f; // Alice 起手相同的答案
            for (int v = 1; v <= n; v++) { // Bob 起手点
                if (u == v) continue;

                /*预处理链上的位置*/
                int pos, i, j;
                if (u > v) pos = u, i = v, j = v + n; else pos = n + u, i = v, j = v + n;

                int now = 0; // 当前答案
                if ((pos - i - 1) % 2 && (j - pos - 1) % 2) {
                    int l = i + (pos - i - 1) / 2;
                    int r = pos + (j - pos) / 2;
                    now = std::max(dis[pos] - dis[l] + dis[r - 1] - dis[pos], dis[r] - dis[pos] + dis[pos] - dis[l + 1]);
                } else if ((pos - i - 1) % 2 == 0 && (j - pos - 1) % 2 == 0) {
                    int l = i + (pos - i - 1) / 2;
                    int r = pos + (j - pos - 1) / 2;
                    now = dis[pos] - dis[l] + dis[r] - dis[pos];
                } else {
                    int l, r;
                    if ((pos - i - 1) % 2) l = i + (pos - i - 1) / 2, r = pos + (j - pos - 1) / 2;
                    else r = pos + (j - pos) / 2, l = i + (pos - i - 1) / 2;
                    now = dis[pos] - dis[l] + dis[r] - dis[pos];
                }
                res = std::min(res, now);
            }
        printf("%lld", res);
    } else {
        int ans = 0;
        for (int u = 1; u <= n; u++) { // Alice 起手点
            int res = 0x3f3f3f3f3f3f3f3f; // Alice 起手相同的答案
            for (int v = 1; v <= n; v++) { // Bob 起手点
                if (u == v) continue;

                /*预处理链上的位置*/
                int pos, i, j;
                if (u > v) pos = u, i = v, j = v + n; else pos = n + u, i = v, j = v + n;

                int now = 0; // 当前答案
                if ((pos - i - 1) % 2 && (j - pos - 1) % 2) {
                    int l = i + (pos - i - 1) / 2;
                    int r = pos + (j - pos) / 2;
                    now = std::max(dis[pos] - dis[l] + dis[r - 1] - dis[pos], dis[r] - dis[pos] + dis[pos] - dis[l + 1]);
                } else if ((pos - i - 1) % 2 == 0 && (j - pos - 1) % 2 == 0) {
                    int l = i + (pos - i - 1) / 2;
                    int r = pos + (j - pos - 1) / 2;
                    now = dis[pos] - dis[l] + dis[r] - dis[pos];
                } else {
                    int l, r;
                    if ((pos - i - 1) % 2) l = i + (pos - i - 1) / 2, r = pos + (j - pos - 1) / 2;
                    else r = pos + (j - pos) / 2, l = i + (pos - i - 1) / 2;
                    now = dis[pos] - dis[l] + dis[r] - dis[pos];
                }
                res = std::min(res, now);
            }
            ans = std::max(ans, res);
        }
        printf("%lld", ans);
    }

    return 0;
}

\(\rm{T3}\)

从这里开始都是纯暴力, 给 \(\rm{T2}\)\(1 \ \rm{h}\) 左右打的时间, 主要问题是 \(\rm{T2}\) 思路是否正确, 看起来很对啊

\(\rm{T2}\) 优化的时间应该是没有了, 但是拿 \(60 \ \rm{pts}\) 是可观的

思路

打个 \(20 \ \rm{pts}\) , 直接跑路

框架

首先读入
然后暴力

代码

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

int n, m, k;
bool mp[MAXN][MAXN];
int cnt = 0;

struct posters { int w, h; } posts[2];

signed main()
{
    scanf("%lld %lld %lld", &n, &m, &k);
    if (k == 1) {
        scanf("%lld %lld", &posts[1].w, &posts[1].h);
        printf("%lld", posts[1].w * posts[1].h);
    } else if (k == 2) {
        scanf("%lld %lld", &posts[1].w, &posts[2].w); scanf("%lld %lld", &posts[1].h, &posts[2].h);
        for (int i = 1; i <= posts[1].w; i++) for (int j = 1; j <= posts[1].h; j++) mp[i][j] = true;
        for (int i = n - posts[2].w + 1; i <= n; i++) for (int j = m - posts[2].h + 1; j <= m; j++) mp[i][j] = true;
        for (int i = 1; i <= n; i++) for (int j = 1; j <= n; j++) if (mp[i][j]) cnt++;

        printf("%lld", cnt);
    } else {
        printf("520641");
    }

    return 0;
}

\(\rm{T4}\)

还是纯暴力

目前得分 \(70 \ \rm{pts}\)

思路

你注意到 \(\mathcal{O} (nq)\) 很好做啊, 拼上一个 \(m = 1\) 不是无敌了

考虑 \(m = 1\) 怎么做, \(\mathcal{O} (nq)\) 是模拟算法
我们标记当前的前后缀分割点, 然后每次旋转相当于把分割点往后调了一位

于是处理就简单了

实现

框架

对于 \(\mathcal{O} (nq)\) 是模拟算法
对于 \(m = 1\) 按照上面的做就可以了

\(20 \ \rm{min}\) 极速通关

哦哦, 看反了, \(m = 1\) 的操作是把最后一个丢到前面去, 但是代码也是同理的

代码

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

int n, m, q;
struct node { int c, a; } ret[MAXN];

/*暴力模拟*/
class subtask1
{
private:
    std::vector<int> pos;

public:
    void solve() {
        while (q--) {
            int op; scanf("%lld", &op);
            if (op == 1) {
                int l, r; scanf("%lld %lld", &l, &r);
                int ans = 0;
                for (int i = l; i <= r; i++) ans += ret[i].a;
                printf("%lld\n", ans);
            } else {
                int x; scanf("%lld", &x);
                pos.clear();
                for (int i = 1; i <= n; i++) if (ret[i].c == x) pos.push_back(i);
                for (int i = pos.size() - 1; i > 0; i--) std::swap(ret[pos[i]], ret[pos[i - 1]]);
            }
        }
    }
} sub1;

class subtask2
{
private:
    int pre[MAXN], suf[MAXN];
    /*预处理前后缀和*/
    void init() {
        pre[0] = suf[0] = 0;
        for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + ret[i].a;
        for (int i = n; i >= 1; i--) suf[n - i + 1] = suf[n - i] + ret[i].a;
    }

public:
    void solve() {
        init();
        int x = 1; // 分割点位置
        while (q--) {
            int op; scanf("%lld", &op);
            if (op == 1) {
                int l, r; scanf("%lld %lld", &l, &r);
                int ans = 0;
                if (r < x) ans = suf[x - l] - suf[x - r - 1];
                else if (l < x && r >= x) ans = suf[x - l] + pre[r - x + 1];
                else ans = pre[r - x + 1] - pre[l - x];
                printf("%lld\n", ans);
            } else {
                int bin; scanf("%lld", &bin);
                x++;
                x = x > n ? 1 : x;
            }
        }
    }
} sub2;

signed main()
{
    scanf("%lld %lld %lld", &n, &m, &q);
    for (int i = 1; i <= n; i++) scanf("%lld", &ret[i].c); for (int i = 1; i <= n; i++) scanf("%lld", &ret[i].a);

    if (n <= 1000 && q <= 1000) sub1.solve();
    else if (m == 1) sub2.solve();

    return 0;
}

先这样, 去做 \(\rm{T2}\)

总结

以后的策略都这样了, 加油

我有绝对不能输的理由!

posted @ 2025-01-03 11:38  Yorg  阅读(16)  评论(0)    收藏  举报