Solution Set -「NOIP Simu.」20221003
二分图排列
定义一个数列 合法, 当且仅当无向图 是二分图. 现给定 的排列 , 你可以将其中任意多个数取相反数得到 , 求所有合法的 中字典序最小的一个.
多测, .
Tags:「A.DP-序列 DP」「C.性质/结论」
显然, 若 中存在大小为 的环, 则一定存在大小为 的环. 因而合法条件等价于不存在三元环, 也即是不存在 使得 .
先来看看怎么判断合法. 我们从左到右扫描 , 扫描到 时, 维护出一个数对 , 其中 , . 可见, 意味着非法三元组的出现; 若 , 则 , 否则 . 如果扫描顺利完成, 这个序列就是合法的.
现在, 我们想要构造字典序最小的 , 求解过程中, 我们会尝试确定一段前缀 , 并决策当前的数取 或 , 同时检查后缀能否在此基础上取出合法序列. 那么, 对应到合法判断方式, 我们会将扫描到 时获得的 作为后缀的输入数对, 判断这个输入数对能否在后缀中通过判断.
接下来就是状态设计. 最原始的自然是 表示从 开始, 输入数对为 时, 后缀是否有合法解; 注意到 在对方固定时, 自己越大越劣, 所以可以优化成形如 表示从 开始, 输入数对为 , 时才有合法解; 再进一步, 注意在完成 上的判断后, 必然有一者为 或者 , 所以状态可以简化为: 表示从 开始, 输入数对的 能够被替换为 时, 的最大可行值. 暴力讨论 种转移对应的条件即可 转移.
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
inline char fgc() {
static char buf[1 << 17], *p = buf, *q = buf;
return p == q && (q = buf + fread(p = buf, 1, 1 << 17, stdin), p == q) ?
EOF : *p++;
}
template <typename Tp = int>
inline Tp rint() {
Tp x = 0, s = fgc(), f = 1;
for (; s < '0' || '9' < s; s = fgc()) f = s == '-' ? -f : f;
for (; '0' <= s && s <= '9'; s = fgc()) x = x * 10 + (s ^ '0');
return x * f;
}
template <typename Tp>
inline void wint(Tp x) {
if (x < 0) putchar('-'), x = -x;
if (9 < x) wint(x / 10);
putchar(x % 10 ^ '0');
}
template <typename Tp>
inline void chkmin(Tp& u, const Tp& v) { v < u && (u = v, 0); }
template <typename Tp>
inline void chkmax(Tp& u, const Tp& v) { u < v && (u = v, 0); }
template <typename Tp>
inline Tp imin(const Tp& u, const Tp& v) { return u < v ? u : v; }
template <typename Tp>
inline Tp imax(const Tp& u, const Tp& v) { return u < v ? v : u; }
const int MAXN = 1e6, IINF = 0x3f3f3f3f;
int n, a[MAXN + 5];
/**
* 0: max available U when D=-a[i]
* 1: max available D when U=-a[i]
* 2: max available U when D=a[i]
* 3: max available D when U=a[i]
*/
int f[MAXN + 5][4];
#define chkif(a, b, c) void((c) && (chkmax(a, b), 0))
int main() {
for (int T = rint(); T--;) {
n = rint();
rep (i, 1, n) a[i] = rint();
f[n][0] = f[n][2] = IINF, f[n][1] = -a[n], f[n][3] = a[n];
per (i, n - 1, 1) {
rep (j, 0, 3) f[i][j] = -IINF;
chkif(f[i][0], f[i + 1][0], -a[i] < -a[i + 1]);
chkif(f[i][0], -a[i + 1], -a[i] <= f[i + 1][1]);
chkif(f[i][0], f[i + 1][2], -a[i] < a[i + 1]);
chkif(f[i][0], a[i + 1], -a[i] <= f[i + 1][3]);
chkif(f[i][1], -a[i + 1], -a[i] <= f[i + 1][0]);
chkif(f[i][1], f[i + 1][1], -a[i] < -a[i + 1]);
chkif(f[i][1], a[i + 1], -a[i] <= f[i + 1][2]);
chkif(f[i][1], f[i + 1][3], -a[i] < a[i + 1]);
chkif(f[i][2], f[i + 1][0], a[i] < -a[i + 1]);
chkif(f[i][2], -a[i + 1], a[i] <= f[i + 1][1]);
chkif(f[i][2], f[i + 1][2], a[i] < a[i + 1]);
chkif(f[i][2], a[i + 1], a[i] <= f[i + 1][3]);
chkif(f[i][3], -a[i + 1], a[i] <= f[i + 1][0]);
chkif(f[i][3], f[i + 1][1], a[i] < -a[i + 1]);
chkif(f[i][3], a[i + 1], a[i] <= f[i + 1][2]);
chkif(f[i][3], f[i + 1][3], a[i] < a[i + 1]);
chkmin(f[i][1], -a[i]), chkmin(f[i][3], a[i]);
if (f[i][0] < -a[i]) f[i][0] = -IINF;
if (f[i][2] < a[i]) f[i][2] = -IINF;
// printf("%d: %d %d %d %d\n",
// i, f[i][0], f[i][1], f[i][2], f[i][3]);
}
bool sol = false;
rep (i, 0, 3) sol |= f[1][i] != -IINF;
if (!sol) { puts("NO"); continue; }
puts("YES");
int u = -IINF, d = -IINF;
rep (i, 1, n) {
int x;
if ((d <= -a[i] && u <= f[i][0])
|| (u <= -a[i] && d <= f[i][1])) {
x = -a[i];
} else {
x = a[i];
}
if (x > u) u = x;
else d = x;
wint(x), putchar(' ');
}
putchar('\n');
}
return 0;
}
最短路问题 V3
给定含有 个点 条边的带权连通无向图, 次询问两点最短路.
, .
Tag:「水题无 tag」
拿树边之外的 个点暴力 Dijkstra. 复杂度 .
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
typedef long long LL;
typedef std::pair<LL, int> PLI;
#define fi first
#define se second
inline char fgc() {
static char buf[1 << 17], *p = buf, *q = buf;
return p == q && (q = buf + fread(p = buf, 1, 1 << 17, stdin), p == q) ?
EOF : *p++;
}
template <typename Tp = int>
inline Tp rint() {
Tp x = 0, s = fgc(), f = 1;
for (; s < '0' || '9' < s; s = fgc()) f = s == '-' ? -f : f;
for (; '0' <= s && s <= '9'; s = fgc()) x = x * 10 + (s ^ '0');
return x * f;
}
template <typename Tp>
inline void wint(Tp x) {
if (x < 0) putchar('-'), x = -x;
if (9 < x) wint(x / 10);
putchar(x % 10 ^ '0');
}
const int MAXN = 1e5, MAXLG = 16;
const LL LINF = 1ll << 60;
int n, m, ecnt = 1, head[MAXN + 5];
int stc, dep[MAXN + 5], stn[MAXN + 5], st[MAXLG + 3][MAXN * 2 + 5];
bool ontr[MAXN + 25];
LL dow[MAXN + 5], dis[45][MAXN + 5];
struct Edge { int to, cst, nxt; } graph[(MAXN + 20) * 2 + 5];
std::vector<int> key;
inline void link(const int u, const int v, const int w) {
graph[++ecnt] = { v, w, head[u] }, head[u] = ecnt;
graph[++ecnt] = { u, w, head[v] }, head[v] = ecnt;
}
struct DSU {
int fa[MAXN + 5];
inline void init() { rep (i, 1, n) fa[i] = i; }
inline int find(const int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
inline bool unite(int x, int y) {
if ((x = find(x)) == (y = find(y))) return false;
return fa[x] = y, true;
}
} dsu;
inline void init(const int u, const int fa) {
st[0][stn[u] = ++stc] = u, dep[u] = dep[fa] + 1;
for (int i = head[u], v; i; i = graph[i].nxt) {
if (ontr[i >> 1] && (v = graph[i].to) != fa) {
dow[v] = dow[u] + graph[i].cst, init(v, u), st[0][++stc] = u;
}
}
}
inline void initST() {
rep (i, 1, 31 - __builtin_clz(stc)) {
rep (j, 1, stc - (1 << i) + 1) {
st[i][j] = dep[st[i - 1][j]] < dep[st[i - 1][j + (1 << i >> 1)]]
? st[i - 1][j] : st[i - 1][j + (1 << i >> 1)];
}
}
}
inline int lca(int u, int v) {
if ((u = stn[u]) > (v = stn[v])) std::swap(u, v);
int k = 31 - __builtin_clz(v - u + 1);
return dep[st[k][u]] < dep[st[k][v - (1 << k) + 1]] ?
st[k][u] : st[k][v - (1 << k) + 1];
}
inline LL dist(const int u, const int v) {
return dow[u] + dow[v] - 2 * dow[lca(u, v)];
}
inline void dijkstra(const int s, LL* ds) {
std::priority_queue<PLI, std::vector<PLI>, std::greater<PLI>> heap;
rep (i, 1, n) ds[i] = LINF;
heap.emplace(ds[s] = 0, s);
while (heap.size()) {
PLI p(heap.top()); heap.pop();
if (p.fi != ds[p.se]) continue;
for (int i = head[p.se], v; i; i = graph[i].nxt) {
LL d = p.fi + graph[i].cst;
if (ds[v = graph[i].to] > d) heap.emplace(ds[v] = d, v);
}
}
}
int main() {
n = rint(), m = rint(), dsu.init();
rep (i, 1, m) {
int u = rint(), v = rint(), w = rint();
link(u, v, w);
if (!(ontr[i] = dsu.unite(u, v))) key.push_back(u), key.push_back(v);
}
init(1, 0), initST();
std::sort(key.begin(), key.end());
key.resize(std::unique(key.begin(), key.end()) - key.begin());
rep (i, 0, int(key.size()) - 1) dijkstra(key[i], dis[i]);
for (int q = rint(); q--;) {
int u = rint(), v = rint();
LL ans = dist(u, v);
rep (i, 0, int(key.size()) - 1) {
ans = std::min(ans, dis[i][u] + dis[i][v]);
}
wint(ans), putchar('\n');
}
return 0;
}
捡石子游戏
给定一棵含有 个点的树, 点 有点权 . Alice 和 Bob 博弈, Alice 选择一个起点 , 此后 Alice 和 Bob 轮流操作:
-
令 ;
-
取一个 的邻接点 , 令 . 若此时 , 则当前操作者获胜.
求出使得 Alice 必胜的所有起点或声明无解.
.
Tag:「C.性质/结论」
注意一个必败情况: Alice 陷入了一个 "山谷" , . 那么不管 Alice 怎么挣扎, Bob 只需要在自己的回合将 移回 , Alice 就不可能先于 Bob 取胜.
进一步讨论发现, 双方都不可能将 移动向 , 使得 , 因为这样的移动肯定会被对手反向移动消除, 而 的减小更可能让自己陷入 "山谷". 换句话说, 从 开始的移动必然走向使得 的 .
这里就能直接 DP 了. 按点权升序枚举点, 检查四周能走到的点的胜负态情况. 除排序外可以做到 .
/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
const int MAXN = 3e3;
int n, val[MAXN + 5], ecnt, head[MAXN + 5], ord[MAXN + 5], sg[MAXN + 5];
struct Edge { int to, nxt; } graph[MAXN * 2 + 5];
inline void link(const int u, const int v) {
graph[++ecnt] = { v, head[u] }, head[u] = ecnt;
graph[++ecnt] = { u, head[v] }, head[v] = ecnt;
}
int main() {
scanf("%d", &n);
rep (i, 1, n) scanf("%d", &val[i]);
rep (i, 2, n) { int u, v; scanf("%d %d", &u, &v), link(u ,v); }
std::iota(ord + 1, ord + n + 1, 1);
std::sort(ord + 1, ord + n + 1,
[](const int u, const int v) { return val[u] < val[v]; }
);
rep (i, 1, n) {
int u = ord[i];
static std::bitset<MAXN + 5> vis; vis.set();
for (int j = head[u], v; j; j = graph[j].nxt) {
if (val[v = graph[j].to] < val[u]) {
vis.reset(sg[v]);
}
}
sg[u] = vis._Find_first();
}
bool exi = false;
rep (i, 1, n) if (sg[i]) {
if (exi) putchar(' ');
exi = true, printf("%d", i);
}
puts(exi ? "" : "-1");
return 0;
}
凹函数 *
求严格单增凹函数 最多过多少个整点.
询问次数 , .
Tags:「A.DP-杂项」「B.Tricks」
稍作简化, 我们要求最大的一组 使得 , .
直接 DP 是 左右的, 注意到凸包大小是 的, 因此可以记 表示 , 向量组数为 时 的最小值. 到此可以做到 .
考虑从最优性上继续剪枝. 注意选出 时, 一定已经选掉了所有合法的 . 进而可以用 进一步限制 的可选性. 可以证明, 此时被用来更新背包的向量只有 个, 因此最终复杂度为 .
Proof
设总向量数量为 , 那么/*+Rainybunny+*/
#include <bits/stdc++.h>
#define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i)
#define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i)
template <typename Tp>
inline void chkmin(Tp& u, const Tp& v) { v < u && (u = v, 0); }
template <typename Tp>
inline void chkmax(Tp& u, const Tp& v) { u < v && (u = v, 0); }
template <typename Tp>
inline Tp imin(const Tp& u, const Tp& v) { return u < v ? u : v; }
template <typename Tp>
inline Tp imax(const Tp& u, const Tp& v) { return u < v ? v : u; }
const int N = 3e3, IINF = 0x3f3f3f3f;
std::vector<int> ans[N + 5];
inline void update(const int x, const int y) {
// printf("%d %d\n", x, y);
per (i, N, x) {
rep (j, 0, int(ans[i - x].size()) - 1) {
if (ans[i - x][j] + y > N) break;
if (ans[i].size() == j + 1) ans[i].push_back(IINF);
chkmin(ans[i][j + 1], ans[i - x][j] + y);
}
}
}
inline void solve() {
static int sum[N + 5][2];
rep (i, 0, N) ans[i].push_back(0);
rep (i, 1, N) {
int s[2] = {};
rep (j, 1, N) {
s[0] += sum[j][0], s[1] += sum[j][1];
if (s[0] > N || s[1] > N) break;
if (std::__gcd(i, j) == 1) {
s[0] += i, s[1] += j, sum[j][0] += i, sum[j][1] += j;
if (s[0] > N || s[1] > N) break;
update(i, j);
}
}
}
}
int main() {
solve();
int q; scanf("%d", &q);
while (q--) {
int n, m; scanf("%d %d", &n, &m);
printf("%d\n", int(std::upper_bound(ans[n].begin(),
ans[n].end(), m) - ans[n].begin()));
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2021-10-03 Involuting Bunny! (2021.9)