Loading

[PAKEN Training Camp 2024 Day 3] K. 門松

前言

要在段中
要在段中
摸完这节课, 开始整段之后一定要在段中

思路

首先是并非求最优解

首先显然有构造方式: 首先对本就满足答案的位置进行赋值, 然后其他的交换一次赋值即可
这样上界是 \(2n - 2\) 次操作, 寄

考虑怎么优化
找点性质

套路
  • 定义操作, 要求把 aba \to b
    • 打印操作方法
      • 考虑一种构造策略
    • 图论转化
      • 约束仅存在于操作顺序/每个元素的状态上
        • 考虑对约束顺序/状态建图判环
    • 直接正向处理
      • 构造方式
        • 往往应该把选择权留到后面去

这种限制操作次数的问题, 往往需要将单操作的效益变大, 例如本题除去赋值操作之后, 只剩下 \(\dfrac{n}{2}\) 的操作次数

考虑让一次操作确定两个位置
进行一些模拟之后发现, 操作会对后面的位置产生有限影响
用有向图刻画影响, 然后拓扑排序确定顺序即可

代码
#include "bits/stdc++.h"
const int MAXN = 2e5 + 20;
#define b(i) (p[i] > p[i + 1])

#define pcd(i) ((i == end || i == 0) ? (true) : (done[i] && done[i - 1]))
#define lftrow(i) (!done[i - 1])
#define rgtrow(i) (!done[i])
#define row(i) ((!done[i]) && (!done[i - 1]))

#define CHECKPRE (!pcd(pred) && p[d] > p[pred])
#define CHECKNXT (!pcd(nxtd) && p[d] > p[nxtd])

int n;
int p[MAXN], c[MAXN];
bool done[MAXN];
int end;

std::vector<std::pair<int, int> > ans;

namespace change {
    void mdfrow(int d) {
        if (p[d - 1] < p[d + 1]) std::swap(p[d - 1], p[d]), ans.push_back({1, d - 1}), ans.push_back({2, d - 1}), ans.push_back({2, d});
        else std::swap(p[d], p[d + 1]), ans.push_back({1, d}), ans.push_back({2, d - 1}), ans.push_back({2, d});
    }
    void mdflftrow(int d) { std::swap(p[d - 1], p[d]), ans.push_back({1, d - 1}), ans.push_back({2, d - 1}); }
    void mdfrgtrow(int d) { std::swap(p[d], p[d + 1]), ans.push_back({1, d}), ans.push_back({2, d}); }
} using namespace change;

class GraphMaker {
private:
    int n, m; // n 为点数, m 为边数
    struct EdgeMaker { int to, nxt; };
    std::vector<int> head, ind; std::vector<EdgeMaker> edge;

public:
    GraphMaker(int n) : n(n) { head.resize(n + 1, -1), edge.resize(MAXN << 1), ind.resize(n + 1, 0); }

    void addedge(int u, int v) { edge[++m] = {v, head[u]}, head[u] = m, ind[v]++; }

    std::vector<int> topo() {
        std::vector<int> ret; std::queue<int> q;
        for (int i = 1; i <= n; i++) if (ind[i] == 0 && i % 2 == 0) q.push(i);

        while (!q.empty()) {
            int u = q.front(); q.pop();
            ret.push_back(u);
            for (int e = head[u]; ~e; e = edge[e].nxt) {
                int v = edge[e].to;
                if (--ind[v] == 0) q.push(v);
            }
        }
        return ret;
    }
};

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &p[i]);
    for (int i = 1; i < n; i++) scanf("%d", &c[i]);
    if (n % 2 == 0) p[n + 1] = -0x3f3f3f3f, c[n] = 1;
    end = n + 1; if (n % 2 == 0) end++;

    for (int i = 1; i < n; i++) if (b(i) == c[i]) done[i] = true, ans.push_back({2, i});
    done[n] = true;
    GraphMaker gra(n);
    auto build = [&]() -> void {
        for (int d = 2; d <= n; d += 2) {
            int nxtd = d + 2, pred = d - 2;
            if (pcd(d)) continue;
            if (row(d)) {
                if (p[d - 1] < p[d + 1]) { if (CHECKPRE) gra.addedge(pred, d); }
                else if (CHECKNXT) gra.addedge(nxtd, d);
            } else if (lftrow(d)) { if (CHECKPRE) gra.addedge(pred, d); }
            else if (rgtrow(d)) { if (CHECKNXT) gra.addedge(nxtd, d); }
        }
    }; build();

    auto solve = [&]() -> void {
        std::vector<int> tpn = gra.topo();
        for (auto d : tpn) {
            if (pcd(d)) continue;
            if (row(d)) mdfrow(d);
            else if (lftrow(d)) mdflftrow(d);
            else if (rgtrow(d)) mdfrgtrow(d);
        }
        printf("%d\n", ans.size());
        for (auto [op, d] : ans) printf("%d %d\n", op, d);
    }; solve();

    return 0;
}

总结

限制操作次数的问题, 往往需要将单操作的效益变大

posted @ 2025-04-15 15:53  Yorg  阅读(10)  评论(0)    收藏  举报