Solution Set -「NOIP Simu.」20221003
\(\mathscr{A}\sim\) 二分图排列
定义一个数列 \(\{a_n\}\) 合法, 当且仅当无向图 \(G=(\{1..n\},\{(i,j)\mid i<j\land a_i>a_j\})\) 是二分图. 现给定 \(1\sim n\) 的排列 \(\{a_n\}\), 你可以将其中任意多个数取相反数得到 \(\{a_n'\}\), 求所有合法的 \(\{a_n'\}\) 中字典序最小的一个.
多测, \(\sum n\le10^6\).
Tags:「A.DP-序列 DP」「C.性质/结论」
显然, 若 \(G\) 中存在大小为 \(k\) 的环, 则一定存在大小为 \(k-1\) 的环. 因而合法条件等价于不存在三元环, 也即是不存在 \(i<j<k\) 使得 \(a_i'>a_j'>a_k'\).
先来看看怎么判断合法. 我们从左到右扫描 \(\{a_n'\}\), 扫描到 \(i\) 时, 维护出一个数对 \((u,d)\), 其中 \(u=\max_{j<i}\{a_j'\}\), \(d=\max_{j<i}\{a_j'\mid \exists k<j,~a_k'>a_j'\}\). 可见, \(d>a_i\) 意味着非法三元组的出现; 若 \(a_i>u\), 则 \(u\gets a_i\), 否则 \(d\gets a_i\). 如果扫描顺利完成, 这个序列就是合法的.
现在, 我们想要构造字典序最小的 \(\{a_n'\}\), 求解过程中, 我们会尝试确定一段前缀 \(a_{1..i-1}'\), 并决策当前的数取 \(+a_i\) 或 \(-a_i\), 同时检查后缀能否在此基础上取出合法序列. 那么, 对应到合法判断方式, 我们会将扫描到 \(a_{1..i}'\) 时获得的 \((u,d)\) 作为后缀的输入数对, 判断这个输入数对能否在后缀中通过判断.
接下来就是状态设计. 最原始的自然是 \(f(i,u,d)\) 表示从 \(i\) 开始, 输入数对为 \((u,d)\) 时, 后缀是否有合法解; 注意到 \(u,d\) 在对方固定时, 自己越大越劣, 所以可以优化成形如 \(f(i,u)\) 表示从 \(i\) 开始, 输入数对为 \((u,d_0)\), \(d_0\le f(i,u)\) 时才有合法解; 再进一步, 注意在完成 \(i\) 上的判断后, \((u,d)\) 必然有一者为 \(+a_i\) 或者 \(-a_i\), 所以状态可以简化为: \(f(i,0\sim3)\) 表示从 \(i\) 开始, 输入数对的 \(u/d\) 能够被替换为 \(+a_i/-a_i\) 时, \(d/u\) 的最大可行值. 暴力讨论 \(4^2\) 种转移对应的条件即可 \(\mathcal O(n)\) 转移.
/*+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;
}
\(\mathscr{B}\sim\) 最短路问题 V3
给定含有 \(n\) 个点 \(m\) 条边的带权连通无向图, \(q\) 次询问两点最短路.
\(n,q\le10^5\), \(m\le n+20\).
Tag:「水题无 tag」
拿树边之外的 \(40\) 个点暴力 Dijkstra. 复杂度 \(40\times\mathcal O(n\log n)\).
/*+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;
}
\(\mathscr{C}\sim\) 捡石子游戏
给定一棵含有 \(n\) 个点的树, 点 \(u\) 有点权 \(w_u\). Alice 和 Bob 博弈, Alice 选择一个起点 \(s\), 此后 Alice 和 Bob 轮流操作:
-
令 \(w_s\gets w_s-1\);
-
取一个 \(s\) 的邻接点 \(t\), 令 \(s\gets t\). 若此时 \(w_t=0\), 则当前操作者获胜.
求出使得 Alice 必胜的所有起点或声明无解.
\(n\le3\times10^3\).
Tag:「C.性质/结论」
注意一个必败情况: Alice 陷入了一个 "山谷" \(u\), \(\forall (u,v)\in E,~w_v\ge w_u\). 那么不管 Alice 怎么挣扎, Bob 只需要在自己的回合将 \(s\) 移回 \(u\), Alice 就不可能先于 Bob 取胜.
进一步讨论发现, 双方都不可能将 \(s\) 移动向 \(t\), 使得 \(w_s\le w_t\), 因为这样的移动肯定会被对手反向移动消除, 而 \(w_s\) 的减小更可能让自己陷入 "山谷". 换句话说, 从 \(s\) 开始的移动必然走向使得 \(w_t<w_s\) 的 \(t\).
这里就能直接 DP 了. 按点权升序枚举点, 检查四周能走到的点的胜负态情况. 除排序外可以做到 \(\mathcal O(n)\).
/*+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;
}
\(\mathscr{D}\sim\) 凹函数 *
求严格单增凹函数 \(f:[0,n]\to[0,m]\) 最多过多少个整点.
询问次数 \(q\le10^4\), \(n,m\le3\times10^3\).
Tags:「A.DP-杂项」「B.Tricks」
稍作简化, 我们要求最大的一组 \(\{(x,y)_k\}\) 使得 \(x_i\perp y_i\), \(\sum x\le n,\sum y\le m\).
直接 DP 是 \(\mathcal O(n^4)\) 左右的, 注意到凸包大小是 \(\mathcal O(n^{2/3})\) 的, 因此可以记 \(f(i,j)\) 表示 \(\sum x=i\), 向量组数为 \(j\) 时 \(\sum y\) 的最小值. 到此可以做到 \(\mathcal O(n^{3+2/3})\).
考虑从最优性上继续剪枝. 注意选出 \((x,y)\) 时, 一定已经选掉了所有合法的 \((x',y')<(x,y)\). 进而可以用 \(\sum x',\sum y'\) 进一步限制 \((x,y)\) 的可选性. 可以证明, 此时被用来更新背包的向量只有 \(\mathcal O(n^{2/3})\) 个, 因此最终复杂度为 \(\mathcal O(n^{7/3})\).
Proof
设总向量数量为 $C$, 那么 $$ \begin{aligned} C &\le \sum_{x=1}^n\sum_{y=1}^n[x\perp y]\left[\sum_{x'=1}^x\sum_{y'=1}^y[x'\perp y'](x'+y')\le2n\right]\\ &\le \sum_{x=1}^n\sum_{y=1}^n\left[\sum_{x'=1}^x\sum_{y'=1}^y[x'\perp y'](x'+y')\le2n\right]\\ &\sim \sum_{x=1}^n\sum_{y=1}^n\left[\sum_{d}\mu(d)\cdot y/d\cdot d\sum_{x'=1}^{x/d}x'\le n\right]\\ &\sim \sum_{x=1}^{n}\sum_{y=1}^n\left[y\sum_{d}\mu(d)(x/d)^2\le n\right]\\ &\sim \sum_{x=1}^n\left[\sum_{y=1}^nyx^2\cdot\frac{\pi^2}{6}\le n\right]\\ &= \mathcal O(n^{2/3}). \end{aligned} $$/*+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;
}