10.16 CSP-S 模拟赛总结

T1

我很神秘。数据水理论复杂度 \(O(nk^2)\) 暴力过了。

实际上只要想到对 \(k\) 取模就会做了。因为满足要求的情况即为存在一段 \([l,r]\) 的子区间和对 \(k\) 取模为 \(0\),那么等价于两次前缀和对 \(k\) 取模模数相同。

T2

注意到值域为 \(2 \times 10^6\),考虑直接枚举 \(g\)。那么我们需要的就是 \(g\) 或者 \(g\) 的正整数倍被区间包含的次数,即我们只需要记右端点出现位置次数的前缀和,考虑 \(g,k\) 的关系即可。

  • \(g > k\)

    枚举正整数倍 \(\mu g\),统计 \([\mu g, \mu g + k]\) 的次数和。

  • \(g \leq k\)

    对于 \(b_i < g\) 一定是不满足的,所以只需要看在值域上的后缀和即可。

如果满足次数和 \(cnt \geq n - f\),说明可以满足删除 \(f - \omega (\omega \geq 0)\) 个之后用剩下公约数为 \(g\) 的补齐。

T3

加强数据之后暴力还是能过(

很显然直接暴力是 \(O(nmq)\) 的。可能可以猜到拆成横纵处理或许最后是 \(O((n + m)q)\),惊鸿一瞥发现是可以通过的复杂度。

子矩阵交换之后本质上内部的相对位置是不改变的,且题目保证交换的两个矩阵不相交。只有外面一圈的信息会改变。考虑用链表维护子矩阵周围一圈的点的右、下指针。

具体的,找到 \((x_1,y_1), (x_2, y_2)\) 在链表的具体位置。\((x_1,y_1)\) 在向右跳的同时交换下指针,向下跳的时候交换右指针。\((x_2,y_2)\) 向下跳的时候交换右指针,在向右跳的同时交换下指针。

:::info[Code]

#include <bits/stdc++.h>
#define int long long
#define ID(i, j) ((i) * (m + 1) + (j))
using namespace std;
const int MaxN = 1e3 + 10;
int n, m, q;
string v[MaxN][MaxN];
struct LIST {
    int right, down;
    string value;
} L[MaxN * MaxN];

signed main() {
    // freopen("file.in", "r", stdin);
    // freopen("file.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> v[i][j];
            L[ID(i, j)].value = v[i][j];
        }
    }
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            L[ID(i, j)].right = ID(i, j + 1);
            L[ID(i, j)].down = ID(i + 1, j);
        }
    }

    for (int _ = 1; _ <= q; _++) {
        int a, b, c, d, h, w;
        cin >> a >> b >> c >> d >> h >> w;

        int ptr1 = 0, ptr2 = 0;
        for (int i = 1; i < a; i++) ptr1 = L[ptr1].down;
        for (int i = 1; i < b; i++) ptr1 = L[ptr1].right;
        for (int i = 1; i < c; i++) ptr2 = L[ptr2].down;
        for (int i = 1; i < d; i++) ptr2 = L[ptr2].right;

        int x = ptr1, y = ptr2;
        for (int i = 1; i <= w; i++) {
            x = L[x].right, y = L[y].right;
            swap(L[x].down, L[y].down);
        }
        for (int i = 1; i <= h; i++) {
            x = L[x].down, y = L[y].down;
            swap(L[x].right, L[y].right);
        }

        x = ptr1, y = ptr2;
        for (int i = 1; i <= h; i++) {
            x = L[x].down, y = L[y].down;
            swap(L[x].right, L[y].right);
        }
        for (int i = 1; i <= w; i++) {
            x = L[x].right, y = L[y].right;
            swap(L[x].down, L[y].down);
        }
    }

    int x = 0;
    for (int i = 1; i <= n; i++) {
        x = L[x].down;
        int y = x;
        for (int j = 1; j <= m; j++) {
            y = L[y].right;
            cout << L[y].value << " \n"[j == m];
        }
    }
    return 0;
}

T4

读完题就觉得相当的随机化啊。

\(25\%\) 可以简单随机化做。

考虑使用模拟退火,你发现它 TLE 了,而且是小数据 TLE 了。转用爬山,因为是求一个直线以上的答案且只要满足答案在直线以上即可。所以局部最优解并非不对。同时在计算满足的三元组个数时考虑使用桶来记每个元素的对应三元组编号,这样复杂度就是平均下来的 \(O(\frac{n}{m})\) 可视作常数。

:::info[Code]

#include <bits/stdc++.h>
#define int long long
using namespace std;

namespace FASTIO {
    static short ostk[33];
    char buf[1 << 23], *p1, *p2;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 23, stdin), p1 == p2) ? EOF : *p1++)

    inline int read() {
        int res = 0, f = 1;
        char ch = getchar();
        while (!isdigit(ch)) f = ch == '-' ? -1 : 1, ch = getchar();
        while (isdigit(ch)) res = res * 10 + (ch ^ 48), ch = getchar();
        return res * f;
    }
    inline void write(int x) {
        int top = 0;
        if (x < 0) x = -x, putchar('-');
        do { ostk[top++] = x % 10, x /= 10; } while(x);
        while(top) putchar(ostk[--top] | 48);
    }
} using namespace FASTIO;

random_device seed;
mt19937 rng(seed());
const int MaxN = 1e5 + 10;
const double omega = 0.998244353, eps = 1e-10;
int n, m, a[MaxN], pos[MaxN], ans;
vector<int> rec[MaxN];
bitset<MaxN> used;
struct NODE {
    int a, b, c;
} p[MaxN];

inline int rd(int l, int r) {
    return 1LL * (rng() % (r - l + 1)) + l;
}

bool check(int x) {
    int a = pos[p[x].a],
        b = pos[p[x].b],
        c = pos[p[x].c];
    return (a < b && b < c) || (c < b && b < a);
}

int counter(int x, int y) {
    int ret = 0;
    used.reset();
    for (int v : rec[x]) {
        if (!used[v]) {
            ret += check(v);
            used[v] = 1;
        }
    }
    for (int v : rec[y]) {
        if (!used[v]) {
            ret += check(v);
            used[v] = 1;
        }
    }
    return ret;
}

int getall() {
    int ret = 0;
    for (int i = 1; i <= m; i++)
        ret += check(i);
    return ret;
}

void hike() {
    int ret = ans;
    double T = 1e3;
    while (T >= eps) {
        if (ret >= (m + 1) / 2) break;

        int x = rd(1, n),
            y = rd(1, n),
            fir = 0, sec = 0;
        fir = counter(x, y);
        swap(pos[x], pos[y]);
        sec = counter(x, y);
        swap(a[x], a[y]);
        int delta = fir - sec;
        if (delta < 0)
            ret += (-delta);
        else {
            swap(a[x], a[y]);
            swap(pos[x], pos[y]);
        }
        T *= omega;
    }
    ans = max(ans, ret);
}

signed main() {
    n = read();
    m = read();
    for (int i = 1; i <= m; i++) {
        p[i].a = read();
        p[i].b = read();
        p[i].c = read();
        rec[p[i].a].push_back(i);
        rec[p[i].b].push_back(i);
        rec[p[i].c].push_back(i);
    }
    for (int i = 1; i <= n; i++) 
        a[i] = pos[i] = i;
    ans = getall();
    while (clock() * 1.0 / CLOCKS_PER_SEC < 0.8) {
        if (ans >= (m + 1) / 2) break;
        hike();
    }
    if (ans >= (m + 1) / 2) {
        for (int i = 1; i <= n; i++)
            a[pos[i]] = i;
        for (int i = 1; i <= n; i++)
            write(a[i]), putchar(' ');
    }
    return 0;
}
posted @ 2025-10-17 12:12  xAlec  阅读(10)  评论(0)    收藏  举报