[PAKEN Training Camp 2024 Day 3] K. 門松
前言
要在段中
要在段中
摸完这节课, 开始整段之后一定要在段中
思路
首先是并非求最优解
首先显然有构造方式: 首先对本就满足答案的位置进行赋值, 然后其他的交换一次赋值即可
这样上界是 \(2n - 2\) 次操作, 寄
考虑怎么优化
找点性质
套路
- 定义操作, 要求把
- 打印操作方法
- 考虑一种构造策略
- 图论转化
- 约束仅存在于操作顺序/每个元素的状态上
- 考虑对约束顺序/状态建图判环
- 约束仅存在于操作顺序/每个元素的状态上
- 直接正向处理
- 构造方式
- 往往应该把选择权留到后面去
- 构造方式
- 打印操作方法
这种限制操作次数的问题, 往往需要将单操作的效益变大, 例如本题除去赋值操作之后, 只剩下 \(\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;
}
总结
限制操作次数的问题, 往往需要将单操作的效益变大