-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 131 KB
/
content.json
1
{"posts":[{"title":"51nod1376","text":"题意 序列中有多少个 \\(\\rm LIS\\)(位置不同就算做不同)? https://www.51nod.com/Challenge/Problem.html#problemId=1376 解答 如果遇到过的话应该很容易,单纯想放在博客站上而已。 直接考虑树状数组合并 LIS 的过程,注意本题需要离散化。 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667#include <bits/stdc++.h>using namespace std;static constexpr int N = 500007;static constexpr int mod = 1000000007;struct Info { int a, b; Info(int _a = 0, int _b = 0) : a(_a), b(_b) {} Info operator+=(const Info& y) { if (b < y.b) { return *this = y; } else if (b > y.b) { return *this; } else { return *this = Info((a + y.a) % mod, b); } }};int tot, n, a[N], b[N];Info c[N];void mdf(int i, Info val) { for (; i <= tot; i += i & -i) { c[i] += val; }}Info qry(int i) { Info ans{}; for (; i; i -= i & -i) { ans += c[i]; } return ans;}int main() { cin.tie(nullptr)->sync_with_stdio(false); int n; cin >> n; for (int i = 1; i <= n; ++i) { cin >> a[i]; } copy(a + 1, a + 1 + n, b + 1); sort(b + 1, b + 1 + n); tot = unique(b + 1, b + 1 + n) - b - 1; Info ans{}; for (int i = 1; i <= n; ++i) { int l = lower_bound(b + 1, b + 1 + tot, a[i]) - b; Info cur = qry(l - 1); cur.a = max(cur.a, 1); cur.b += 1; ans += cur; mdf(l, cur); } cout << ans.a << "\\n"; return 0;}","link":"51nod/1376/"},{"title":"CC2020-ChangChun-A","text":"题意 购买优惠券,以下是首充优惠。手冲,嘿嘿 🤤。 \\[ \\begin{array}{|c|c|c|} \\hline \\text{Price (RMB yuan)} & \\text{Normal amount (coupons)} & \\text{First recharge reward (coupons)} \\\\ \\hline 1 & 10 & 8 \\\\ \\hline 6 & 60 & 18 \\\\ \\hline 28 & 280 & 28 \\\\ \\hline 88 & 880 & 58 \\\\ \\hline 198 & 1980 & 128 \\\\ \\hline 328 & 3280 & 198 \\\\ \\hline 648 & 6480 & 388 \\\\ \\hline \\end{array} \\] 例如 \\(¥100\\) 可以购买 \\(\\underbrace{880 + 58}_{88} + \\underbrace{60 + 18}_{6} + \\underbrace{10 + 8}_{1} + 5 \\times 10 = 1084\\) https://codeforces.com/gym/102832/problem/A 解答 实际上是最大化首充优惠,是一个标准的 \\(0\\text-1\\) 背包问题。 CC2020-ChangChun-A.cpp >folded1234567891011121314151617181920#include <bits/stdc++.h>int main() { ::std::cin.tie(nullptr)->sync_with_stdio(false); int n; ::std::cin >> n; static int f[2020]; const int w[] = {8, 18, 28, 58, 128, 198, 388}; const int v[] = {1, 6 , 28, 88, 198, 328, 648}; for(int i = 0; i < 7; ++i) { for(int j = n; ~j; --j) { if(j >= v[i]) { f[j] = ::std::max(f[j], f[j - v[i]] + w[i]); } } } ::std::cout << n * 10 + f[n] << '\\n';}","link":"cc/2020/changchun/a/"},{"title":"CC2020-ChangChun-D","text":"题意 定义函数 \\[ a_n = \\begin{cases} 1, & n = 0 \\\\ c \\cdot \\max\\limits_{0 \\le i < n} a_{n \\operatorname{\\&} i}, & \\text{otherwise} \\end{cases}, \\] 求 \\(\\left( \\displaystyle\\sum\\limits_{i=0}^n a_i \\right) \\bmod (10^9+7)\\) https://codeforces.com/gym/102832/problem/D \\(0 \\le n \\lt 2 ^ { 3000 }\\) \\(0 \\le c \\le 10 ^ 9\\) 解答 尝试手推 \\(\\{a\\}\\) 的前几项(\\(c = 2\\)): 下标 \\(0\\) \\(1\\) \\(2\\) \\(3\\) \\(4\\) \\(5\\) \\(6\\) \\(7\\) 二进制 \\(0\\) \\(1\\) \\(10\\) \\(11\\) \\(100\\) \\(101\\) \\(110\\) \\(111\\) 数值 \\(1\\) \\(2\\) \\(2\\) \\(4\\) \\(2\\) \\(4\\) \\(4\\) \\(8\\) 即 \\(a_i = c ^ {\\operatorname{popcount}(i)}\\)。 首先注意到 \\(n\\) 全一的情况是二项式定理的经典形式: \\[ \\sum_{i = 0}^n \\binom{n}{i} p ^ i = (1 + p) ^ n \\] 而如果有某些位不是 \\(1\\),则可以按位拆分为多个上述的和式,即对于所有 \\(n_i = 1\\) 的位: \\[ \\verb|calc|(i) = \\left( (c + 1) ^ {N - i - 1} + c \\times \\verb|calc|(i - 1) \\right) \\] CC2020-ChangChun-D.cpp >folded123456789101112131415161718192021#include <bits/stdc++.h>static char n[3333];int c, f = 1, g = 1;const int mod = 1E9 + 7;int main() { scanf("%s%d", n + 1, &c); int N = strlen(n + 1); for (int i = N; i; --i) { if (n[i] == '1') { f = (1LL * f * c + g) % mod; } g = 1LL * g * (c + 1) % mod; } printf("%d", f); return 0;}","link":"cc/2020/changchun/d/"},{"title":"CC2020-ChangChun-F","text":"题意 \\[ \\sum\\limits_{i=1}^n\\sum\\limits_{j=i+1}^n [a_i \\oplus a_j = a_{\\operatorname{lca}(i, j)}] (i \\oplus j) \\] https://codeforces.com/gym/102832/problem/F 欢迎收看大型节目——全场都会 DSU on tree。 \\(2 \\le n \\le 10 ^ 5\\) \\(a \\le a_i \\le 10^6\\) 解答 首先拆位。 子树静态信息统计,选用 DSU on tree 为佳。直接计算以当前点为 \\(\\operatorname{lca}\\) 的贡献。记下来每一位的 \\(0/1\\) 数量之后。 CC2020-ChangChun-F.cpp >folded123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778#include <bits/stdc++.h>auto main() -> int { ::std::cin.tie(nullptr)->sync_with_stdio(false); int n; ::std::cin >> n; ::std::vector w(n + 1, 0); for (int i = 1; i <= n; ++i) { ::std::cin >> w[i]; } ::std::vector g(n + 1, ::std::vector(0, 0)); for (int i = 1; i < n; ++i) { int u, v; ::std::cin >> u >> v; g[u].push_back(v); g[v].push_back(u); } ::std::vector size(n + 1, 0), son(n + 1, 0); ::std::function<void(int, int)> dfs = [&](int u, int p) -> void { size[u] = 1; for (int v : g[u]) if (v != p) { dfs(v, u); size[u] += size[v]; if (size[v] > size[son[u]]) { son[u] = v; } } }; dfs(1, 0); long long ans { }; int cur { }, top { }; static int cnt[1000110][21][2] { }; ::std::vector stk(n + 1, 0); ::std::function<void(int, int, bool)> dsu = [&](int u, int p, bool kp) -> void { for (int v : g[u]) if (v != p && v != son[u]) dsu(v, u, false); if (son[u]) dsu(son[u], u, true), cur = son[u]; ::std::function<void(int, int, int)> calc = [&](int u, int p, int root) -> void { if (p == root) { for (; top; top --) { int t = stk[top]; for (int i = 0; i < 20; ++i) { cnt[w[t]][i][t >> i & 1] += 1; } } } for (int i = 0; i < 20; ++i) { // 此处小心越界 if (int p = w[u] ^ w[root]; p <= 1000000) { ans += 1LL * cnt[p][i][~u >> i & 1] * (1 << i); } } stk[ ++ top ] = u; for (int v : g[u]) if (v != p && v != cur) calc(v, u, root); }; calc(u, p, u), cur = 0; for (; top; -- top) { int t = stk[top]; for (int i = 0; i < 20; ++i) { cnt[w[t]][i][t >> i & 1] ++; } } ::std::function<void(int, int)> del = [&](int u, int p) -> void { for (int i = 0; i < 20; ++i) cnt[w[u]][i][u >> i & 1] -= 1; for (int v : g[u]) if (v != p && v != cur) del(v, u); }; if (!kp) del(u, p); }; dsu(1, 0, false); ::std::cout << ans << "\\n"; return {};}","link":"cc/2020/changchun/f/"},{"title":"CF1311F","text":"题意 数轴上有若干点,第 \\(i\\) 个点初始坐标为 \\(x_i\\),速度为 \\(v_i\\)。\\(t\\) 时刻的坐标为 \\(x_i + t \\times v_i\\)。 初始时坐标两两不同,问对于任意两个点任意时刻可能的最短距离 \\(d(i, j)\\) 之和。即求 \\[ \\sum_{1 \\le i \\lt j \\le n} d(i, j) \\] 解答 因为 \\(x\\) 初始两两不同,不妨设 \\(x_i < x_j\\)。若 \\(v_i \\le v_j\\),距离差不会变得更小,于是最短距离就是初态的距离。否则必定相遇,即距离差最小为 \\(0\\)。 因而问题转变为满足 \\((x_i, v_i) \\prec (x_j, v_j) \\xlongequal{\\rm equals\\;\\;to} x_i \\lt x_j \\land v_i \\le v_j\\) 的点之间的距离。 于是需要维护「满足偏序的点对数」及「其各点到原点的距离」两个信息。如果设两部分分别为 \\(c, d\\),那么答案就是 \\(c \\times d_0 - d\\),其中 \\(d_0\\) 为当前点的坐标。 本题保证 \\(x\\) 两两不同,因此是最基础的二维偏序,无需在排序时分类。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define V views#define RI R::iota_viewstruct bit { int n; vector<ll> tr; bit(int _n) : n(_n), tr(_n + 1, 0){}; void mdf(int i, int x) { for (; i <= n; i += i & -i) { tr[i] += x; } } auto qry(int i) { ll ans{}; for (; i; i -= i & -i) { ans += tr[i]; } return ans; }};int main() { int n; cin >> n; vector a(n, pair{0, 0}); vector<int> v(n); for (int i : RI(0, n)) { cin >> a[i].first; } for (int i : RI(0, n)) { cin >> a[i].second; v[i] = a[i].second; } R::sort(a); R::sort(v); v.resize(unique(v.begin(), v.end()) - v.begin()); // count, distance bit c(n), d(n); ll ans{}; for (int i : RI(0, n)) { int l = 1 + R::lower_bound(v, a[i].second) - v.begin(); c.mdf(l, 1); d.mdf(l, a[i].first); ans += c.qry(l) * a[i].first - d.qry(l); } cout << ans << "\\n"; return 0 ^ 0;}","link":"cf/1311/f/"},{"title":"CF1313C1","text":"题意 是 CF1313C2 的简单版。","link":"cf/1313/c1/"},{"title":"CF1313C2","text":"题意 建造摩天楼,每一块地上最高能建造的摩天楼层数 \\(a_i \\le m_i\\)。且不能出现 \\(i < j < k \\land a_i > a_j < a_k\\) 的情况,即不能有「一幢楼左右都有比他高的大厦(不必相邻)」。 求 \\(\\max \\displaystyle \\left\\{\\sum_{i = 1}^n\\{A\\}\\right\\}\\) 对应的一组方案。 解答 答案一定形如 \\(/\\backslash\\) 先单调不减、再单调不增。假定非也,则必有这样的答案是更优的解。 于是,对于每一个 \\(a_i\\) 都需要找到它左边最近的 \\(j\\) 使得 \\(a_j \\lt a_i\\),即 nge。这一段的解就可以全部更改为 \\(a_i\\)。 也就是以 \\(i\\) 为最高点的一段答案为 \\(l_{[\\operatorname{nge}^L_il]} = a_i \\times (i - \\operatorname{nge}^L_i)\\)。由于上述讨论分段的特性,因而,实际上 \\(l_i\\) 是一个前缀和。即 \\[ l_i = a_i \\times (i - \\operatorname{nge}^L_i) + l_{\\operatorname{nge}^L_i} \\] 同理有,右半部分的后缀和: \\[ r_i = a_i \\times (\\operatorname{nge}^R_i - i) + r_{\\operatorname{nge}^R_i} \\] 使用单调栈求得两个方向的 \\(\\operatorname{nge}\\)。 综合两部分来看,以 \\(i\\) 为最高点的答案实际上就是 \\(\\color{red}\\boxed{l_i + r_i - a_i}\\)。最大的点即为所求,设为 \\(M\\)。 求得 \\(M\\) 之后,向左右做后缀、前缀最小值就可以了。思路并不困难,编码时需要仔细,不要写错了。 展开参考代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define V views#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int n; cin >> n; vector<int> a(n + 1); for (int i : RI(1, n + 1)) { cin >> a[i]; } vector<int> stk(n + 1); int top{}; vector<int> nxt(n + 1, n + 1); for (int i : RI(1, n + 1)) { while (top && a[stk[top]] > a[i]) { nxt[stk[top--]] = i; } stk[++top] = i; } vector<ll> s(n + 1); for (int i : RI(1, n + 1) | V::reverse) { s[i] = 1LL * a[i] * (nxt[i] - i) + s[nxt[i]]; } top = 0; vector<int> pre(n + 1); for (int i : RI(1, n + 1) | V::reverse) { while (top && a[stk[top]] > a[i]) { pre[stk[top--]] = i; } stk[++top] = i; } vector<ll> p(n + 1); for (int i : RI(1, n + 1)) { p[i] = 1LL * a[i] * (i - pre[i]) + p[pre[i]]; } vector<ll> dp(n + 1); for (int i : RI(1, n + 1)) { dp[i] = p[i] + s[i] - a[i]; } int M = R::max_element(dp | V::drop(1)) - dp.begin(); vector<int> ans(n + 1); for (int pre = a[M]; int i : RI(1, M) | V::reverse) { ans[i] = (pre = min(pre, a[i])); } for (int pre = a[M]; int i : RI(M + 1, n + 1)) { ans[i] = (pre = min(pre, a[i])); } ans[M] = a[M]; R::copy(ans | V::drop(1), ostream_iterator<int>(cout, " ")); return 0;}","link":"cf/1313/c2/"},{"title":"CF1320C","text":"题意 从若干剑与盾中各买一把,决定你的 🗡️ 攻击力与 🛡 防御力。随后讨伐若干 👾。如果攻击力、防御力都大于某 👾,将获得对应的 💰 赏金。求最大利益,即获得的赏金减去购买武器的花费。 剑、盾、怪兽的数量均低于 \\(2 \\times 10 ^ 5\\) 👾 的 🗡️ 攻击力和 🛡 防御力均 \\(\\le 10 ^ 6\\) 而勇士的均低于 \\(10 ^ 9\\) 💰 收益 \\(\\le 10 ^ 3\\) 解答 .WAV { text-decoration-style: dotted; } 依然是二维偏序问题,但本题并不直接数点。而是对于固定的 \\(a\\) 寻找使得利益最大化的 \\(d\\)。其中 \\(a, d\\) 为攻击力attack和防御力defense。为方便考虑,首先对于数据与询问都按照 \\(a\\) 排序,同时注意到怪兽全体攻防力都不大,可以免去离散化。 对于每个 \\(d\\) 都能使用二分求出最小能打败怪兽的防御值 \\(d\\),选取最大的收益。对于任何大于当前 \\((a, d)\\) 的组合都可加上此贡献。即区间加数 + 区间最大值询问,上线段树。 CF1320C.cpp >folded123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109// g++ CF1320C.cpp -std=c++23 -Os -o CF1320C#include <bits/stdc++.h>using namespace std;using ll = long long;const int maxn = 2E5 + 5;const ll INF = 1E18;int n, m, p;array<ll, 2> a[maxn], d[maxn];array<ll, 3> q[maxn];struct segment { int l, r; ll max, tag;} T[maxn << 2];#define ls i << 1#define rs ls | 1auto pushup(int i) { T[i].max = max(T[ls].max, T[rs].max);}void build(int i, int l, int r) { if (T[i] = {.l = l, .r = r}; l == r) { T[i].max = -d[l][1]; return; } int mid = (l + r) / 2; build(ls, l, mid); build(rs, mid + 1, r); pushup(i);}void pushdn(int i) { if (T[i].tag) { T[ls].max += T[i].tag; T[rs].max += T[i].tag; T[ls].tag += T[i].tag; T[rs].tag += T[i].tag; T[i].tag = 0; }}void upd(int i, int l, int r, int k) { if (T[i].l >= l && T[i].r <= r) { T[i].max += k; T[i].tag += k; return; } pushdn(i); int mid = (T[i].l + T[i].r) / 2; if (l <= mid) { upd(ls, l, r, k); } if (r > mid) { upd(rs, l, r, k); } pushup(i);}int main() { cin.tie(nullptr)->sync_with_stdio(false); cin >> n >> m >> p; for (int i = 1; i <= n; i++) { cin >> a[i][0] >> a[i][1]; } for (int i = 1; i <= m; i++) { cin >> d[i][0] >> d[i][1]; } for (int i = 1; i <= p; i++) { cin >> q[i][0] >> q[i][1] >> q[i][2]; } sort(d + 1, d + 1 + m); sort(a + 1, a + 1 + n); sort(q + 1, q + 1 + p); build(1, 1, m); ll l = 1, ans = -INF; // 固定攻击力 for (int i = 1; i <= p && l <= n; i++) { // 跳过所有 (a, d) 打不过怪兽的点 while (l <= n && q[i][0] >= a[l][0]) { ans = max(ans, T[1].max - a[l++][1]); } // 找到最小能打掉怪兽的 d 随后将后续区间 [g, m] 都加上 q[i][2] if (int g = lower_bound(d + 1, d + 1 + m, array<ll, 2>{q[i][1], {}}) - d; g <= m) { upd(1, g, m, q[i][2]); } } for (int i = l; i <= n; i++) { ans = max(ans, T[1].max - a[i][1]); } cout << ans << "\\n"; return 0;}","link":"cf/1320/c/"},{"title":"CF1579E1","text":"题意 给定一个排列 \\(\\{P\\}_n\\),按照顺序向双端队列的头或者尾插入元素,问能得到的字典序最小的双端队列。 解答 用一个双端队列 std::deque 模拟。如果当前的队首比新增加的元素大,就将元素插入队首;否则插入队尾。 展开参考代码 12345678910111213141516171819202122232425262728293031#include <bits/stdc++.h>using namespace std;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n; cin >> n; deque<int> dq; for (int x; int i : RI(0, n)) { cin >> x; if (dq.empty() || dq.front() < x) { dq.push_back(x); } else if (dq.front() > x) { dq.push_front(x); } } for (int i : RI(0, n)) { cout << dq[i] << " \\n"[i + 1 == n]; } } return 0 ^ 0;}","link":"cf/1579/e1/"},{"title":"CF1407D","text":"题意 有 \\(n\\) 个点,每个点具有高度 \\(h_i\\)。如果可以从 \\(i\\) 跳到 \\(j\\;(i \\lt j)\\),则要么相邻、要么中间所有元素比 \\(h_i, h_j\\) 都高或者都低。问从 \\(1\\) 跳到 \\(n\\) 的最小步数。 解答 相邻点更新:dp[i] = dp[i - 1] + 1。 后两者实际上需要找到「最近更大/小元素」(即 nge),这是单调栈的基本问题。设当前枚举到 \\(h_i\\),从 nge 跳过来就可以了。 123456789// 一直向前找while (t1 && a[i] >= a[s1[t1]]) { // 相等: 用最后一个点 i 直接覆盖前面的, 不用管 if (int x = s1[t1--]; a[i] != a[x] && t1) { dp[i] = min(dp[i], dp[s1[t1]] + 1); }}// 相等s1[++t1] = i; 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int n; cin >> n; vector a(n, 0); for (int i : RI(0, n)) { cin >> a[i]; } vector dp(n, numeric_limits<int>::max()); vector s1(n + 1, 0), s2(n + 1, 0); int t1{}, t2{}; s1[++t1] = s2[++t2] = dp[0] = 0; for (int i : RI(1, n)) { dp[i] = dp[i - 1] + 1; while (t1 && a[i] >= a[s1[t1]]) { if (int x = s1[t1--]; a[i] != a[x] && t1) { dp[i] = min(dp[i], dp[s1[t1]] + 1); } } s1[++t1] = i; while (t2 && a[i] <= a[s2[t2]]) { if (int x = s2[t2--]; a[i] != a[x] && t2) { dp[i] = min(dp[i], dp[s2[t2]] + 1); } } s2[++t2] = i; } cout << dp.back() << "\\n"; return 0 ^ 0;}","link":"cf/1407/d/"},{"title":"CF1579E2","text":"题意 给定一个序列 \\(\\{A\\}_n\\),按照顺序向双端队列的头或者尾插入元素,问能得到的逆序对数最小的双端队列。 \\(1 \\le n \\le 2 \\times 10 ^ 5\\) \\(-10 ^ 9 \\le a_i \\le 10 ^ 9\\) 解答 与 /cf/1579/e1/思路是类似的。 考虑到放头尾分别的贡献会对下一阶段的决策产生影响(逆序对数变了),但决策方式是一样的(新增的元素对逆序对数的影响仅限于原来的元素),即只需分别判定放头尾哪个更小。 元素很大,需要离散化。 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n; cin >> n; vector a(n, 0); for (int i : RI(0, n)) { cin >> a[i]; } // 离散化超简洁写法 auto b {a}; R::sort(b); b.resize(unique(b.begin(), b.end()) - b.begin()); struct bit { int n; vector<int> tr; bit(int _n) : n(_n), tr(_n + 1, 0){}; void mdf(int i, int x) { for (; i <= n; i += i & -i) { tr[i] += x; } } int qry(int i) { int ans{}; for (; i; i -= i & -i) { ans += tr[i]; } return ans; } } g(b.size()); ll ans{}; for (int i : RI(0, n)) { int l = 1 + R::lower_bound(b, a[i]) - b.begin(); g.mdf(l, 1); // [0, l), [l, m] ans += min(g.qry(l - 1), g.qry(b.size()) - g.qry(l)); } cout << ans << "\\n"; } return 0 ^ 0;}","link":"cf/1579/e2/"},{"title":"CF1712A","text":"题意 在排列 \\(\\{P\\}\\) 上进行若干次交换,使得前 \\(k\\) 项之和最小的操作次数? 解答 由于是排列,将 \\(1 \\sim k\\) 的元素换到前 \\(k\\) 即可。 展开参考代码 1234567891011121314151617181920#include <bits/stdc++.h>using namespace std;using namespace std::placeholders;int main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n, k; cin >> n >> k; vector a(n, 0); for (int & i: a) { cin >> i; } cout << ranges::count_if(a | views::take(k), bind(greater {}, _1, k)) << "\\n"; } return 0;} Bonus 分别给出 \\(1 \\sim k\\) 的答案。 Solution 如果依然是排列,那么结论不变。对前缀 \\(1 \\sim x\\) 中出现的 \\(1 \\sim x\\) 的数的数量进行询问是数状数组的基本问题。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <bits/stdc++.h>using namespace std;#define R std::ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n, k; cin >> n >> k; vector a(n, 0); for (int & i: a) { cin >> i; } struct bit { int n; vector < int > tr; bit(int _n): n(_n + 1), tr(_n + 1, 0) {}; auto qry(int p) { int res {}; for (int i = p; i; i -= i & -i) { res += tr[i]; } return res; } void mdf(int p, int x) { for (int i = p; i <= n; i += i & -i) { tr[i] += x; } } } g(n); for (int i: RI(0, k)) { g.mdf(a[i], 1); cout << i + 1 - g.qry(i + 1) << " \\n" [i + 1 == k]; } } return 0;}","link":"cf/1712/a/"},{"title":"CF1712B","text":"题意 构造排列 \\(\\{P\\}\\) 使得 \\(\\displaystyle \\sum_{i = 1} ^ n \\operatorname{lcm}(i, P_i)\\) 尽可能大。 解答 一个单纯的想法是:按照奇偶性分开。因为 \\(\\operatorname{lcm}(2j, 2k) = 2jk\\) 但如果和奇数 \\(\\operatorname{lcm}\\) 就不会浪费掉 \\(2\\)。 根据 \\(\\operatorname{lcm}(i, i + 1) = i \\times (i + 1)\\)。给出构造,答案为: \\[ \\begin{aligned} & \\left \\{ \\begin{aligned} &\\operatorname{lcm}(1, 1) &\\mathrm{\\;if\\;} n \\equiv 1 \\pmod 2\\\\ 2 \\times &\\operatorname{lcm}(1, 2) &\\mathrm{otherwise} \\end{aligned} \\right . + 2 \\times \\operatorname{lcm}(i, i + 1) + \\cdots + 2 \\times \\operatorname{lcm}(n, n - 1)\\\\ =&\\quad \\color{red}\\boxed{\\dfrac{n(n + 1)(2n + 1)}{6} - \\left\\lfloor\\dfrac{n}{2}\\right\\rfloor} \\end{aligned} \\] 进行任意对换都将破坏系数 \\(2\\),并且只会更小。这样就说明了答案不会更大。 展开参考代码 123456789101112131415161718192021#include <bits/stdc++.h>using namespace std;int main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n; cin >> n; vector a(n, 0); iota(a.begin(), a.end(), 1); for (int i = n % 2; i < n; i += 2) { swap(a[i], a[i + 1]); } ranges::copy(a, ostream_iterator<int>(cout, " ")); cout << "\\n"; } return 0;}","link":"cf/1712/b/"},{"title":"CF1712C","text":"题意 给定正整数序列,可以将某一等值的元素全改成 \\(0\\)。问使得序列非递减的最少次数。 解答 一些事实: 最后的序列中,元素总是成块出现。 如果原数组不满足第 \\(1\\) 条,那么最后的数组前缀有若干 \\(0\\)。 从前向后扫描,遇到某个后缀还未有序,则表明当前点的数应当变为 \\(0\\),直到该元素的最后一次出现。 展开参考代码 1234567891011121314151617181920212223242526272829303132333435363738#include <bits/stdc++.h>using namespace std;#define R ranges#define V views#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n; cin >> n; vector a(n + 1, 0), b { a }, c { a }; for (int i: RI(1, n + 1)) { cin >> a[i]; b[a[i]] = i; } vector s(n + 1, false); s[n] = true; for (int i: RI(1, n) | V::reverse) { s[i] = s[i + 1] & (a[i] <= a[i + 1]); } int ans {}, pos {}; for (int i: RI(1, n + 1)) if (!c[a[i]]) { // 未被移除 if (pos < i && s[i]) { // 好耶! 后面已经有序 break; } ans += c[a[i]] = 1; // 删掉这个元素 pos = max(pos, b[a[i]]); // 直到最后一次出现 } cout << ans << "\\n"; } return 0;}","link":"cf/1712/c/"},{"title":"CF1712D","text":"题意 造出完全图,边长为 \\(\\min\\limits_{i = l}^r a_i\\)。 问至多修改 \\(k\\) 次任意 \\(a_i\\) 后,可能得到的最长直径(最大叶间距)长。 每次修改成 \\([1, 10 ^ 9]\\) 的数字。 解答 设 \\(x\\) 为可能的解,那么存在若干 \\(< x\\) 的解,二分性质是满足的。 多个数做 \\(\\min\\) 操作不会比两个数做 \\(\\min\\) 更好。因此直径端点一定是两相邻元素。这样的路径有两种(当然,可以看作仅有后者): 中途没有经过点。这种情况下只需要修改两端点。 中间经过若干点。此情形下的直径为 \\(2 \\times\\) 中间的最小值(走到前/后面的某个点再回去),需要修改中途所有 \\(2 a_i \\lt x\\) 的点,需要用前缀和、后缀和分别记下这样的点。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <bits/stdc++.h>using namespace std;#define R ranges#define V views#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n, k; cin >> n >> k; vector a(n + 1, 0); for (int i: RI(1, n + 1)) { cin >> a[i]; } int l{1}, r{ 1'000'000'000 }; while (l < r) { auto mid { midpoint(l + 1, r) }; if ([ & ]() -> bool { vector p(n + 2, 0), s(n + 2, 0); // prefix and suffix for (int i: RI(1, n + 1)) { p[i] = p[i - 1] + (a[i] * 2 < mid); } for (int i: RI(1, n + 1) | V::reverse) { s[i] = s[i + 1] + (a[i] * 2 < mid); } for (int i: RI(1, n)) { if (p[i - 1] + s[i + 2] + (a[i] < mid) + (a[i + 1] < mid) <= k) { return true; } } return {}; }()) { l = mid; } else { r = mid - 1; } } cout << l << "\\n"; } return 0;}","link":"cf/1712/d/"},{"title":"CF1712E1","text":"题意 是 CF1712E2 的简单版。","link":"cf/1712/e1/"},{"title":"CF1712E2","text":"题意 \\[ \\displaystyle{\\sum_{i = l}^r \\sum _{j > i}^r \\sum_{k > j}^r}\\Big [ \\operatorname{lcm}(i, j, k) \\ge i + j + k \\Big] = \\;? \\] \\(1 \\le l \\lt r \\le 2 \\times 10 ^ 5.\\) 解答 性质一般,且这样的三元组十分多。反过来却十分少,同时也可充分利用整除偏序,于是求: \\[ \\displaystyle\\binom{r - l}{3} - \\displaystyle{\\sum_{i = l}^r \\sum _{j > i}^r \\sum_{k > j}^r} \\Big [ \\operatorname{lcm}(i, j, k) \\color{red}{\\;<\\;} \\color{black} i + j + k \\Big]. \\] 为叙述方便,\\(\\sout{\\verb|\\operatorname{lcm}(i, j, k)|}\\) 实在太难打了 令 \\(x := \\operatorname{lcm}(i, j, k)\\)。 自然有 \\(x \\color{red}{\\;\\lt\\;} \\color{black} 3k\\),即 \\(x = k \\cup x = 2k\\): \\(x = k\\) 是好满足的,只需要保证 \\(i \\mid k \\cap j \\mid k\\)。 \\(x = 2k\\) 需要 \\(i \\mid x \\cap j \\mid x \\cap i + j > k\\)。 \\(j := \\dfrac{2k}{p} \\Rightarrow \\dfrac{k}{2} \\lt \\dfrac{2k}{p} \\lt k \\Rightarrow 2 \\lt p \\lt 4 \\Rightarrow p = 3\\)。即 \\(\\color{red}\\boxed{ j = \\dfrac{2k}{3} }\\) 类似有 \\(i := \\dfrac{2k}{q} \\Rightarrow \\dfrac{2k}{3} \\gt \\dfrac{2k}{q} \\gt k - j = \\dfrac{k}{3}\\) \\(\\Rightarrow 3 \\lt q \\lt 6 \\Rightarrow \\color{red} \\boxed{ i = \\dfrac{k}{2} \\cup i = \\dfrac{2k}{5}}\\) 综上,答案形如 \\((3, 4, 6)\\) 或 \\((6, 10, 15).\\) 在预处理的时候就可以得到。 另外棘手的问题是区间询问,多组询问,可以从小区间拓展到大区间,这样节省下来许多时间。具体来说,固定右端点,从 \\(N\\) 不断减少到左端点,将经过的所有大于 \\(r\\) 的点按照倍数遍历的方式标记出来,直到下降到左端点 \\(l\\)。 \\(r\\) 左边的所有标记的点加起来就是不合法的情况。可见整个过程需要单点修改区间求和,树状数组恰能胜任。 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define V views#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int t; cin >> t; vector qry(t, tuple { 0, 0, 0 }); vector ans(t, 0 LL); for (int i: RI(0, t)) { int l, r; cin >> l >> r; qry[i] = { --l, r, i }; auto len { 1 LL * r - l }; ans[i] = len * (len - 1) * (len - 2) / 6 - max(0, r / 6 - l / 3) - max(0, r / 15 - l / 6); } R::sort(qry | V::reverse); int N = 200'001; struct bit { int n; vector <ll> tr; bit(int _n): n(_n + 1), tr(_n + 1, 0) {}; auto qry(int p) { ll res {}; for (int i = p; i; i -= i & -i) { res += tr[i]; } return res; } void mdf(int p, int x) { for (int i = p; i <= n; i += i & -i) { tr[i] += x; } } } g(N); vector c(N, 0); // cnt for (auto[l, r, i]: qry) { // static 变量方便下一次询问接力跑 for (static int cur { N }; cur > l; --cur) { for (int j = cur * 2; j <= N; j += cur) { g.mdf(j, c[j]++); } } ans[i] -= g.qry(r); } R::copy(ans, ostream_iterator<ll>{cout, "\\n"}); return 0;}","link":"cf/1712/e2/"},{"title":"CF1717A","text":"题意 计算满足 \\(\\dfrac{\\operatorname{lcm}(a, b)}{\\gcd(a, b)} \\le 3\\) 的 \\((a, b)\\) 对子数量。其中 \\(1 \\le a, b \\le N\\) 。 解答 熟知 \\(\\operatorname{LHS} = \\prod\\limits_{i = 1}^kp_i^{\\max\\{\\alpha_i, \\beta_i\\} - \\min\\{\\alpha_i, \\beta_i\\}}\\)。不妨将每一项都对换,使得 \\(a\\) 中的每一个 \\(\\alpha_i\\) 都不小于 \\(b\\) 中的 \\(\\beta_i\\)。 于是有 \\(\\dfrac{a'}{b'} \\le 3\\),考虑上述操作的顺序影响,答案形如 \\((x, x), (x, 2x)_{cyc}, (x, 3x)_{cyc}\\)。即答案为: \\[ \\Bigg\\lfloor\\frac{N}{1}\\Bigg\\rfloor + \\Bigg\\lfloor\\frac{N}{2}\\Bigg\\rfloor\\times2 + \\Bigg\\lfloor\\frac{N}{3}\\Bigg\\rfloor\\times2 \\] CF1717A.cpp >folded12345678910111213141516171819#include <iostream>auto main() -> int { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int tests; ::std::cin >> tests; while (tests --) { int64_t N; ::std::cin >> N; ::std::cout << (N + N / 2 * 2 + N / 3 * 2) << "\\n"; } return 0;}","link":"cf/1717/A/"},{"title":"CF1717B","text":"题意 构造一个 \\(n \\times n\\) 的矩阵,使得任 \\(k,\\;(k \\mid n)\\) 个相邻的格子中都至少包含一个 X,同时限定 \\((r, c)\\) 位置上为 X。 构造 X 数量最少的方案。 解答 如果没有 \\((r, c)\\) 这一限制的话,只需涂满对角线,答案为 \\(\\dfrac{n ^ 2}{k}\\)。而如果加入此限制,则可以将对角线移动,直至其包含 \\((r, c)\\)。于是,从对角线对应的 \\(y = x\\) 转变为 \\(y - c = x + r\\),那么答案就是所有模 \\(k\\) 同余 \\(r + c\\) 的点了。 CF1717B.cpp >folded123456789101112131415161718192021222324252627#include <iostream>#include <ranges>#include <string>auto main() -> int { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int tests; ::std::cin >> tests; while (tests --) { int N, K, R, C; ::std::cin >> N >> K >> R >> C; auto M = (R + C) % K; for (int i : ::std::ranges::iota_view(1, N + 1)) { ::std::string x(N + 1, 'x'); for (int j : ::std::ranges::iota_view(1, N + 1)) { x[j] = ".X"[(i + j) % K == M]; } ::std::cout << x.substr(1) << "\\n"; } } return 0;}","link":"cf/1717/b/"},{"title":"CF1717C","text":"题意 给定两个等长数组 \\(\\{A\\}, \\{B\\}\\),如果 \\(a_i \\le a_{i + 1}\\) 就可使 \\(a_i := a_i + 1\\),问能否使得 \\(\\{A\\} = \\{B\\}\\) 。 解答 只有增加操作,因此需要保证 \\(a_i \\le b_i\\)。 另一方面,每一个点都被 \\(\\{B\\}\\) 所限制,每一个 \\(a_i\\) 对应的 \\(a_{i + 1}\\) 由 \\(b_{i + 1}\\) 所限制。 如果 \\(a_i \\lt b_i \\gt b_{i + 1} + 1\\),那么 \\(a_i\\) 最多只能变到 \\(b_{i + 1} + 1\\)。这种情况也不行,否则必定有解: 将满足 \\(a_i \\lt b_i \\land a_i \\gt a_{i + 1}\\) 的点连边 \\(i + 1 \\rightarrow i\\),意义为要修改 \\(i\\) 必然在此前修改 \\(i + 1\\)。此情形下原图无环因而必有拓扑序,从而得证。 CF1717C.cpp >folded12345678910111213141516171819202122232425262728293031323334353637#include <iostream>#include <ranges>#include <vector>auto main() -> int { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int tests; ::std::cin >> tests; while (tests --) { int N; ::std::cin >> N; ::std::vector A(N, 0); auto B { A }; for (int &i : A) { ::std::cin >> i; } for (int &i : B) { ::std::cin >> i; } bool ok { true }; for (int i : ::std::ranges::iota_view(0, N)) { // a_i > b_i // a_i < b_i and b_i > b_next + 1 if (A[i] > B[i] or ( A[i] < B[i] and B[i] > B[i == N - 1 ? 0 : i + 1] + 1 )) { ok = false; break; } } ::std::cout << (ok ? "YES\\n" : "NO\\n"); } return 0;}","link":"cf/1717/c/"},{"title":"CF1717D","text":"题意 \\(2 ^ n\\) 个人参加比赛,最终决出一名胜者。\\(A\\) 先操作游戏,操作方式为决定第一场比赛对战双方,并决定每一轮谁胜出。 而 \\(B\\) 后操作游戏,有 \\(k\\) 次交换胜负的机会。 \\(A, B\\) 均知道对方的操作方式,现在 \\(A\\) 希望最终胜出的玩家编号尽可能小,\\(B\\) 则反之。 问最小最终胜出的玩家编号,即经过 \\(A\\) 的安排之后无论 \\(B\\) 如何修改,他总胜。 \\(n \\le 10 ^ 5\\) \\(k \\le \\min\\{2 ^ n, 10 ^ 9\\}\\) 解答 二叉树深度为 \\(n\\),这意味着如果 \\(B\\) 操作次数 \\(k \\ge n\\) ,他将得以令任何人成为赢家。此情形答案为 \\(2 ^ n\\) 否则 \\(A\\) 总可以操纵一些选手,使得他们失败次数 \\(\\gt k\\),这样 \\(B\\) 就无法使得这些选手获胜了。例如将最大编号的一批人安排到第一轮比赛被淘汰的那些人。形式化地说,一个选手需要 \\(i\\) 次修改才能获胜,等价于选定 \\(i\\) 步令其输,这样的选手有 \\(\\displaystyle\\binom{n}{i}\\) 位。 整个过程对于 \\(B\\) 来说,进行一次修改,等价于令编号大 \\(\\displaystyle\\binom{n}{i}\\),综合上面的情形,答案为: \\[ \\sum_{i = 0} ^ {\\min\\{n, k\\}} \\binom{n}{i} \\] CF1717D.cpp >folded12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152#include <iostream>#include <ranges>#include <vector>static constinit int mod = 1E9 + 7;auto main() -> int { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int N, K; ::std::cin >> N >> K; ::std::vector fac(N + 1, 0); auto inf { fac }; // inv-fac fac[0] = 1; for (int i : ::std::ranges::iota_view(1, N + 1)) { fac[i] = 1LL * fac[i - 1] * i % ::mod; } { auto m { ::mod - 2 }; auto n { fac[N] }; auto &A = inf[N]; A = 1; while (m) { if (m & 1) { A = 1LL * A * n % ::mod; } m /= 2; n = 1LL * n * n % ::mod; } } for (int i : ::std::ranges::iota_view(1, N + 1) | ::std::views::reverse) { inf[i - 1] = 1LL * inf[i] * i % mod; } long long ans { }; for (int i : ::std::ranges::iota_view(0, 1 + ::std::min(N, K))) { // binom N i ans += 1LL * fac[N] * inf[i] % ::mod * inf[N - i] % ::mod; ans %= ::mod; } ::std::cout << ans % ::mod << "\\n"; return 0;}","link":"cf/1717/d/"},{"title":"CF1717E","text":"题意 \\[ \\sum_{a + b + c = n} \\operatorname {lcm} (c, \\gcd(a, b)) = ? \\] \\(3 \\le n \\le 10 ^ 5\\) 解答 NlogNlogN 固定 \\(c, \\gcd(a, b) =: d\\),则问题变为求解满足 \\(a + b = n - c \\land (a, b) = d\\) 的 \\((a, b)\\) 对子数,这个数是: \\[ \\varphi\\left(\\dfrac{n - c}{d}\\right) \\] 其中 \\(\\varphi(i)\\) 表示欧拉函数,即 \\(1 \\sim i\\) 中与 \\(i\\) 互质之数的数量。 因此答案就是: \\[ \\sum_{c \\in [2, n)} \\sum_{d \\mid n - c} \\operatorname {lcm} (c, d) \\times \\varphi\\left(\\dfrac{n - c}{d}\\right) \\] 在实现上,先枚举 \\(c\\) 再枚举 \\(n - c\\) 的约数的复杂度带个根号。反过来,先枚举 \\(d\\) 再枚举 \\(d\\) 的倍数更优。 CF1717E.cpp >folded1234567891011121314151617181920212223242526272829303132#include <iostream>#include <ranges>#include <array>#include <numeric>static constinit int mod = 1E9 + 7;auto main() -> int { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int N; ::std::cin >> N; ::std::array<int, 200010> e { }; long long ans { }; for (int i : ::std::ranges::iota_view(2, N)) { e[i] += i - 1; ans += 1LL * e[i] * (N - i) % ::mod; for (int j = 2; j * i <= N; ++j) { ans += 1LL * e[i] * ::std::lcm(j, N - j * i) % ::mod; ans %= ::mod; e[j * i] -= e[i]; } } ::std::cout << ans % mod << "\\n"; return 0;} 解答 NlogN \\[ \\begin{aligned} ans &= \\sum_{k=1}^{n} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} \\colorbox{ADD8E6}{\\boxed{\\operatorname{lcm}(n-ak-bk,k)}} [\\gcd(a,b)=1] [ak+bk \\le n] \\\\ &= \\sum_{k=1}^{n} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} \\colorbox{ADD8E6}{\\boxed{\\dfrac{k(n-ak-bk)}{\\gcd(k,n)}}} \\colorbox{FFF000}{\\boxed{[\\gcd(a,b)=1]}} [ak+bk \\le n] \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} (n - ak - bk)[ak+bk \\le n] \\colorbox{FFF000}{\\boxed{\\sum_{d | \\gcd(a, b)} \\mu(d)}} \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{\\lfloor n / k \\rfloor} \\mu(d) \\sum_{a=1}^{\\lfloor n / kd \\rfloor} \\sum_{b=1}^{\\lfloor n / kd \\rfloor - a} n - akd - bkd \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{\\lfloor{n / k}\\rfloor}\\mu(d)\\left(\\dfrac{n \\lfloor n / kd\\rfloor (\\lfloor n / kd \\rfloor - 1)}{2}+\\dfrac{k d \\lfloor n / kd \\rfloor (1 - {\\lfloor n / kd \\rfloor}^{2})}{3}\\right) \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{b}\\mu(d)\\left(\\dfrac{nt(t - 1)}{2}+\\dfrac{k d t (1 - t^2)}{3}\\right) \\end{aligned} \\] \\((1) \\sim (3)\\) 步骤较为容易,略去不表。 第 \\((4)\\) 步从枚举约数改为枚举倍数,对应的上下限也发生了改变。 第 \\((5)\\) 步展开和式,结果为: \\[ n \\sum_{a = 1}^t\\sum_{b = 1}^{t - a} 1 - kd \\left( \\sum_{a = 1}^t \\sum_{b = 1}^{t - a} a + b\\right) \\] 然后... 第 \\((6)\\) 步做了若干代换,便于代码实现。 CF1717E_mobius.cpp >folded1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465// 实测常数有点大 .... 就当练习了#include <iostream>#include <ranges>#include <vector>#include <cassert>#include <numeric>static constinit int mod = 1E9 + 7;int norm(int x) { return x >= ::mod ? x - ::mod : x < 0 ? x + ::mod : x; }template <class T> T power(T a, int b) { T res = 1; for (; b; b /= 2, a *= a) { if (b % 2) { res *= a; } } return res;}struct modint { int x; modint(int x = 0) : x(norm(x)) {} int val() const { return x; } friend std::ostream &operator<<(std::ostream &os, const modint &x) { return os << x.val(); } modint operator-() const { return modint(norm(::mod - x)); } modint inv() const { assert(x != 0); return power(*this, ::mod - 2); } modint &operator*=(const modint &rhs) { return *this = static_cast<long long>(x) * rhs.x % ::mod;} modint &operator+=(const modint &rhs) { return *this = norm(x + rhs.x); } modint &operator-=(const modint &rhs) { return *this = norm(x - rhs.x); } modint &operator/=(const modint &rhs) { return *this *= rhs.inv(); } friend modint operator+(const modint &lhs, const modint &rhs) { return modint(lhs) += rhs; } friend modint operator-(const modint &lhs, const modint &rhs) { return modint(lhs) -= rhs; } friend modint operator*(const modint &lhs, const modint &rhs) { return modint(lhs) *= rhs; } friend modint operator/(const modint &lhs, const modint &rhs) { return modint(lhs) /= rhs; }};auto main() -> int { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int N; ::std::cin >> N; ::std::vector mu(N + 1, ::modint{ }); mu[1] = 1; for (int i : ::std::ranges::iota_view(2, N + 1)) { mu[i] -= 1; for (int j = i * 2; j <= N; j += i) { mu[j] -= mu[i]; } } ::modint ans { }; for (int k : ::std::ranges::iota_view(1, N + 1)) { int b = N / k; ::modint sum { }; for (int d : ::std::ranges::iota_view(1, b + 1)) { ::modint t = b / d; sum += mu[d] * (t * (t - 1) / 2 * N + t * d * k * (1 - t * t) / 3); } ans += sum * k / ::std::gcd(N, k); } ::std::cout << ans << "\\n"; return 0;}","link":"cf/1717/e/"},{"title":"CF1719A","text":"题意 棋盘上博弈,每次只能向上、向右移动奇数次。谁胜? 解答 到达终点花费 \\(n + m\\) 步。 展开参考代码 123456789101112131415#include <bits/stdc++.h>using namespace std;int main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n, m; cin >> n >> m; cout << (n + m & 1 ? "Burenka\\n" : "Tonya\\n"); } return 0;}","link":"cf/1719/a/"},{"title":"CF1719B","text":"题意 给定 \\(k\\),将 \\(1 \\sim n\\) 的数分为 \\(n / 2\\) 个二元组 \\((a, b)\\),使得 \\(4 \\mid (a + k) \\times b\\)。 或报告不可能。 解答 \\(2 \\times 2 = 1 \\times 4 = 4 \\times 1 = 4\\) 如果 \\(k \\equiv 1 \\pmod 2\\) 可以在 \\((a + k)\\) 与 \\(b\\) 各给出 \\(2\\) 因子的贡献,这样就总有 \\(4 \\mid (a + k) \\times b\\) 了。 否则,如果 \\(4 \\mid k\\),考虑到 \\(k / 4\\) 个二元组 \\((4x, 2n + 1)\\) 可以配对,接下来至多凑出 \\(k / 8\\) 个 \\(2 \\times 2\\) 的二元组。 于是这种情况下凑不出 \\(k / 2\\) 对。 而对于 \\(k \\equiv 2 \\pmod 4\\),则可以用 \\(k / 4\\) 个 \\(4n + 2\\) 凑出 \\(4\\),并且有 \\(k / 4\\) 个 \\(4n\\)。 展开参考代码 123456789101112131415161718192021222324252627#include <bits/stdc++.h>using namespace std;int main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n, k; cin >> n >> k; if (k % 4 == 0) { cout << "NO\\n"; } else { cout << "YES\\n"; for (int i = 2; i <= n; i += 2) { // k 是奇数 或者 i = 4n + 2 if ((k & 1) || (~(i >> 1) & 1)) { cout << i - 1 << " " << i << "\\n"; } else { cout << i << " " << i - 1 << "\\n"; } } } } return 0;}","link":"cf/1719/b/"},{"title":"CF1719C","text":"题意 \\(n\\) 人比赛,战力两两不同。每次从头选择两选手,赢家留在头,输者置于尾。问若干轮后某选手赢了多少局。 解答 事实上,经过 \\(n\\) 局之后,战力最高选手就会一直驻留队首了。后面的选手胜场不会增加,因此使用双端队列模拟这个过程,记录每次的胜者的空间开销也仅为 \\(\\mathcal O(n)\\)。 复杂度为 \\(\\mathcal O(n + q \\log n)\\)。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <bits/stdc++.h>using namespace std;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n, q; cin >> n >> q; int maxpos = -1; deque a(n, pair{0, 0}); for (int i: RI(0, n)) { cin >> a[i].first; if (a[i].first == n) { maxpos = i; } a[i].second = i; } vector b(n, vector<int>{}); for (int i: RI(0, n)) { auto p1 = a.front(); a.pop_front(); auto p2 = a.front(); a.pop_front(); a.push_front(max(p1, p2)); a.push_back(min(p1, p2)); b[max(p1, p2).second].push_back(i); } while (q--) { int i, k; cin >> i >> k; i -= 1; int pos = ranges::upper_bound(b[i], k - 1) - b[i].begin(); cout << pos + (k > n && i == maxpos ? k - n : 0) << "\\n"; } } return 0;}","link":"cf/1719/c/"},{"title":"CF1719D1","text":"题意 是 CF1719D2 的简单版。","link":"cf/1719/d1/"},{"title":"CF1719D2","text":"题意 在数组 \\(\\{A\\}\\) 上选定区间 \\([L, R]\\) 以及数 \\(x\\),花费 \\(\\Big\\lceil\\dfrac{R - L + 1}{2}\\Big\\rceil\\) 将区间中所有数都异或上 \\(x\\)。问至少多少次操作后置空 \\(\\{A\\}\\)? \\(a_i \\in [0, 2 ^ {30})\\) 解答 这种花费与区间长度相关的操作一般来说都可以看作若干长度为 \\(1/2\\) 的区间,反之亦然。 可能使操作次数更少的情形出现在区间异或起来已经是 \\(0\\) 的情况。 分两种情况: 两个相同的数字,这样需要 \\(1\\) 的花费。否则总是若干这样的区间。 若干不同的数字,从头到尾成对异或起来可以节省 \\(1\\) 的花费。 总结两种操作都是能够将花费转变为 \\(\\operatorname{len} - 1\\) 的代价。而核心是,找到一段异或和为 \\(0\\) 的区间,这个可以使用异或前缀和搞定。 设 \\(f_i\\) 为将前缀置空的最小花费,要么将某个数异或自己,要么已经找到一段区间其异或和为 \\(0\\)。 \\(f_i = \\min\\left\\{f_{i - 1} + 1, dp_j + \\operatorname{len}(j + 1, i)\\right\\}\\) 其中 \\(\\bigoplus\\limits_{x = j + 1}^i a_x = 0\\)。 为方便转移,对于每个前缀异或和 \\(p_i\\) 记下 \\(h_{p_i} := f_i - i\\),这样就可以单次在 \\(\\mathcal O(\\log n)\\) 转移。 展开参考代码 1234567891011121314151617181920212223242526272829303132333435363738#include <bits/stdc++.h>using namespace std;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int tests; cin >> tests; while (tests--) { int n; cin >> n; vector<int> a(n), p(n + 1); for (int i: RI(0, n)) { cin >> a[i]; p[i + 1] = p[i] ^ a[i]; } vector<int> f(n + 1, INT_MAX); f[0] = 0; map<int, int> h; h[0] = 0; // f_i - i for (int i: RI(1, n + 1)) { // 把某个数改掉 f[i] = min(f[i], f[i - 1] + !!a[i - 1]); // 区间和为 0 if (h.count(p[i])) { f[i] = min(f[i], h[p[i]] + i - 1); } h[p[i]] = min(h[p[i]], f[i] - i); } cout << f.back() << "\\n"; } return 0;} 对称问题 直接思考「能节省的代价」也不错,思路与上述类似,但实现起来更轻巧。 12345678910for (int i : RI(1, n + 1)) { f[i] = f[i - 1]; if (h.count(p[i])) { dp[i] = max(dp[i], 1 + dp[h[p[i]]]); } // 最后一次出现位置 h[p[i]] = i;}cout << n - f.back(n) << "\\n";","link":"cf/1719/d2/"},{"title":"CF1719E","text":"题意 给定若干字符各 \\(c_i\\) 个,问能否将其排为一串字符,使得每个块内出现的字符数量连缀起来恰为一个斐波那契数列(\\(1, 1, 2, 3, \\cdots\\) )。 如 \\(``abaabbbccccc"\\) 恰好为 \\(\\{1, 1, 2, 3, 5\\}\\) 是一个斐波那契数列。 \\(1 \\le k \\le 100, 1 \\le c_i \\le 10^9\\) 解答 首先可知 \\(\\sum \\{C\\}\\) 必定是某个 \\(\\{\\sum \\operatorname{fibonacci}\\}_i\\)。同时,连续的划分不能是同一个字符。 如 \\(aabbccc\\) 是 \\(\\{2, 2, 3\\}\\)。 倒着考虑。尽管更大的数字有着更多的「选择」,但如果不在正确的位置则一定不行。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); ll fibonacci[66], f1 { 0 }, f2 { 1 }, sum {}; map<ll, int> s; for (int i = 0; i < 66; ++i) { s[sum += (fibonacci[i] = f2)] = i; tie(f1, f2) = tuple { f2, f1 + f2 }; } int tests; cin >> tests; while (tests--) { int n; cin >> n; vector<ll> c(n); for (int i: RI(0, n)) { cin >> c[i]; } ll sum = accumulate(c.begin(), c.end(), 0 LL); if (!s.count(sum)) { cout << "NO\\n"; continue; } for (int i = s[sum], l { -1 }; ~i; --i) { int p { -1 }; for (int j: RI(0, n)) { // 不与上次的重复 // 且还有充足的字符 // 且从未选过或者有更大的选择 if (j != l && c[j] >= fibonacci[i] && (!~p || c[j] > c[p])) { p = j; } } // 查无此项 G! if (!~p) { cout << "NO\\n"; goto G; } l = p; c[p] -= fibonacci[i]; } cout << "YES\\n"; G: ; } return 0;}","link":"cf/1719/e/"},{"title":"CF1A","text":"题意 至少用多少个 \\(a \\times a\\) 的石板,才能完全填满 \\(n \\times m\\) 的矩形? 解答 \\(\\left\\lceil\\dfrac{n}{a}\\right\\rceil \\times \\left\\lceil\\dfrac{m}{a}\\right\\rceil\\) 即为所求。 展开参考代码 123456789101112131415#include <bits/stdc++.h>using ll = long long;using namespace std;int main() { cin.tie(0), ios::sync_with_stdio(0); ll n, m, a; cin >> n >> m >> a; cout << ((n + a - 1) / a) * ((m + a - 1) / a) << "\\n"; return 0 ^ 0;}","link":"cf/1/a/"},{"title":"CF1B","text":"题意 \\(R\\verb|{x}|C\\verb|{y}|\\) 描述坐标,表示在 \\(x\\) 行 \\(y\\) 列。 \\(\\verb|BC|23\\) 则表示第 \\(\\verb|BC|\\) 列第 \\(23\\) 行。其中 \\(\\verb|BC|\\) 的记法如下所述: 第一列被标为 \\(\\verb|A|\\),第二列为 \\(\\verb|B|\\),以此类推,第 \\(26\\) 列为 \\(\\verb|Z|\\)。 接下来为由两个字母构成的列号: 第 \\(27\\) 列为 \\(\\verb|AA|\\),第 \\(28\\) 列为 \\(\\verb|AB| \\cdots\\) 在标为 \\(\\verb|ZZ|\\) 的列之后则由三个字母构成列号,如此类推。 写一个程序,实现两者的转换。 常用的 Excel 表格使用第二种记法。 TODO: [2022-07-28 Patricky] 某年区域赛有个类似的题,记不清了,待补充。 解答 看起来从输入上就已经相对复杂了,不过对于这种格式相对固定的字符串,我们可以使用正则表达式描述之。 \\(C\\) 语言中的 \\(\\verb|scanf(format, args...)|\\) 中的格式串恰好能使用正则表达式进行输入。 这句 scanf(\"%*[A-Z]%d\", &x); 表示的意思就是「忽略所有大写字符,并读入一个整数」。 接下来的部分就是进制转换了,注意这里的进制转换需要处理一下余数为 \\(0\\),即 \\(\\verb|Z|\\) 的情况。 展开参考代码 第七行 \\(\\verb|"%[A-Z]"|\\) 前的一个空格将吞噬所有空白字符。 1234567891011121314151617181920212223242526272829303132333435363738394041#include <bits/stdc++.h>using namespace std;char col[100];int solve() { int a, b = 0; scanf(" %[A-Z]%d%*[A-Z]%d", col, & a, & b); if (b) { string ans; while (b) { int r = b % 26; b /= 26; if (!r) { r = 26; b -= 1; } ans.append(1, r + 'A' - 1); } printf("%s%d\\n", string(ans.rbegin(), ans.rend()).c_str(), a); } else { for (int i = 0, n = strlen(col); i < n; ++i) { b = b * 26 + col[i] - 'A' + 1; } printf("R%dC%d\\n", a, b); } return {};}int main() { int tests; cin >> tests; while (tests--) { solve(); } return 0 ^ 0;}","link":"cf/1/b/"},{"title":"CF1C","text":"题意 找出覆盖给定三角形 \\(\\triangle ABC\\) 的最小正多边形面积。 解答 所求正多边形的顶点一定都在 \\(\\triangle ABC\\) 的外接圆上。 使用反证法容易得到这一点,思路与证明「凸包内最大三角形顶点一定在凸包上」类似。 如何求得三角形对应外接圆的半径?事实上,有正弦定理 \\(\\dfrac{a}{\\sin A} = \\dfrac{b}{\\sin B} = \\dfrac{c}{\\sin C} = 2R\\) 已经给定了三点坐标,可以由向量叉积的几何意义(而不是海伦公式)求得面积: \\[ S = \\dfrac{1}{2} \\left|\\begin{matrix} x_1 &y_1 & 1\\\\ x_2 &y_2 & 1\\\\ x_3 &y_3 & 1\\\\ \\end{matrix} \\right| = \\dfrac{1}{2}\\left|x_1y_2 + x_2y_3 + x_3y_1 - x_1y_3 - x_2y_1 - x_3y_2\\right| \\] 进而转化为 \\(\\dfrac{ab\\sin C}{2}\\) 的形式。总之,有 \\(R = \\dfrac{abc}{4S}\\) 。 接着要求这个外接圆经过三角形顶点最少有多少条边,这是 \\(\\gcd\\) 的基本问题。各边(弦)对应圆心角的最大公约数 \\(t\\) 即为所求。 圆心角怎么求?将弦的两点连结圆心,得到了一个边长为 \\(\\langle R, R, a \\rangle\\) 的三角形,由余弦定理: \\[\\angle BOC = \\arccos\\left(\\dfrac{R^2 + R^2 - a^2}{2R^2}\\right) = \\arccos\\left(1 - \\dfrac{a^2}{2R^2}\\right)\\] 于是该正多边形由 \\(\\dfrac{2\\pi}{t}\\) 个面积为 \\(\\dfrac{R^2\\sin t}{2}\\) 的三角形组成,那么所求便呼之欲出了。 展开参考代码 使用递归 \\(\\gcd\\) 会爆栈(当然不排除是 \\(\\verb|function|\\) 的缘故), 因而笔者改用迭代实现。 注意 \\(\\epsilon\\) 的取值,题目中说: It's guaranteed that the number of angles in the optimal polygon is not larger than \\(100\\). 因而精度不需要控制太高,这反而会造成 \\(\\gcd\\) 迭代次数过多从而导致答案错误。 第三个圆心角需要通过 \\(2\\pi - \\angle AOB - \\angle BOC\\) 得到,否则会有相当大的误差(钝角)。 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253#include <bits/stdc++.h>using namespace std;using db = long double;int main() { struct { db x, y; } P[3] {}; for (int i = 0; i < 3; ++i) { scanf("%Lf%Lf", & P[i].x, & P[i].y); } db C[3] {}; for (int i = 0; i < 3; ++i) { int j = i == 2 ? 0 : i + 1; C[i] = hypot(P[i].x - P[j].x, P[i].y - P[j].y); } // db p = (C[0] + C[1] + C[2]) / 2; // db S = sqrt(p * (p - C[0]) * (p - C[1]) * (p - C[2])); db S {}; for (int i = 0; i < 3; ++i) { int p = !i ? 2 : i - 1, n = i == 2 ? 0 : i + 1; S += P[i].x * P[n].y - P[i].x * P[p].y; } S /= 2; db R = C[0] * C[1] * C[2] / (4 * S); db a[3] {}; a[0] = acos(1 - C[0] * C[0] / (2 * R * R)); a[1] = acos(1 - C[1] * C[1] / (2 * R * R)); a[2] = 2 * numbers::pi_v < db > -a[0] - a[1]; constexpr db epsilon = 1E-2; // 2pi / 100 ~= 0.06283185307 => .01 function<db(db, db)> gcd = [&](db x, db y) -> db { // return fabs(y) < epsilon ? x : gcd(y, fmod(x, y)); => MLE if ((x - y) < -epsilon) { swap(x, y); } while (fabs(y) > epsilon) { tie(x, y) = tuple { y, fmod(x, y) }; } return x; }; db t = gcd(a[0], gcd(a[1], a[2])); printf("%.10Lf", (numbers::pi_v<db> * R * R * sin(t)) / t); return 0 ^ 0;}","link":"cf/1/c/"},{"title":"CF2A","text":"题意 积分板上将记录玩家与每回合的积分变化。问最终获得最高分数的选手中,谁最先达到了最高分数? 解答 因为有负分制,因此需要处理完所有的积分之后再统计最大分数。 随后重新从头向后找,看哪位选手第一个到达了最高分并且他最终获得了最高分。 展开参考代码 1234567891011121314151617181920212223242526272829303132#include <bits/stdc++.h>using namespace std;int main() { cin.tie(0), ios::sync_with_stdio(0); int n; cin >> n; map<string, int> rank, overview; vector g(n, pair<string, int>{}); for (auto &[k, v]: g) { cin >> k >> v; rank[k] += v; } int most = 0; for (auto &[_, v]: rank) { most = max(most, v); } for (auto &[k, v]: g) { overview[k] += v; if (rank[k] == most && overview[k] >= most) { return cout << k << "\\n", int {}; } } return 0 ^ 0;}","link":"cf/2/a/"},{"title":"CF2B","text":"题意 给定方阵,从左上角走到右下角,每次只能向右或是向下。要求经过数之积以尽可能少的 \\(0\\) 结尾,并输出方案。 解答 意即经过的数尽可能少的包含 \\(2\\) 因子与 \\(5\\) 因子。因为答案由他们中的较小值所确定。 只关心 \\(2\\) 与 \\(5\\) 因子的次数,那么问题转换为经典的递推 \\(dp_{i, j} = \\min\\{dp_{i - 1, j}, dp_{i, j - 1}\\}\\)。 因为转移方式单调,要输出路径也只需要反推回去。 有一个小坑点,题目说 non-negative 而不是 positive,如果出现 \\(0\\) 并且 dp 不出一条答案为 \\(0\\) 的方案时,直接走答案为 \\(1\\) 经过 \\(0\\) 的这条路径就好。 展开参考代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677#include <bits/stdc++.h>using namespace std;int main() { cin.tie(0), ios::sync_with_stdio(0); int n; cin >> n; vector dp(n, vector(n, vector < int > (2))); int pos = -1; for (int i = 0; i < n; ++i) { for (int j = 0, x; j < n; ++j) { cin >> x; if (x == 0) { pos = i; } else { for (auto[d, t]: { pair { 2, 0 }, { 5, 1 } }) { while (x % d == 0) { x /= d; dp[i][j][t] += 1; } } } } } for (int i = 1; i < n; ++i) { for (int j: { 0, 1 }) { dp[i][0][j] += dp[i - 1][0][j]; dp[0][i][j] += dp[0][i - 1][j]; } } for (int i = 1; i < n; ++i) { for (int j = 1; j < n; ++j) { for (int k: { 0, 1 }) { dp[i][j][k] += min(dp[i - 1][j][k], dp[i][j - 1][k]); } } } int x = min(0, 1, [ & ](auto x, auto y) { return dp.back().back()[x] < dp.back().back()[y]; }); if (~pos && dp.back().back()[x] > 0) { cout << string { "1\\n" } .append(pos, 'D') .append(n - 1, 'R') .append(n - pos - 1, 'D'); return int {}; } int r = n - 1, c = r; string ans; while (r && c) { if (dp[r - 1][c][x] < dp[r][c - 1][x]) { r -= 1, ans += 'D'; } else { c -= 1, ans += 'R'; } if (!r) { ans += string(c, 'R'); } if (!c) { ans += string(r, 'D'); } } cout << dp.back().back()[x] << "\\n" << string(ans.rbegin(), ans.rend()); return 0 ^ 0;}","link":"cf/2/b/"},{"title":"CF2C","text":"题意 给出三个相外离的圆,求出一个点使得到这三个圆的(分别两条)切线张角相同。 或静默以报告无解。 样例一 背景 如果仅有两个圆,那么该点的运动轨迹也是一个圆。 熟悉解析几何的同学不难发现,此即阿氏圆。 证明是显然的,取半张角,连结切点圆心, 于是得到两个相似三角形,这同时也意味着欲求动点 \\(X\\) 到两圆心(定点)的距离比为定值 (当然,当该比值不为 \\(1\\),否则由反演的性质将退化为一条直线)。 而这正是阿圆的定义。 解答 本题有模拟退火的解法,在此略去。 设三点圆心分别为 \\(A, B, C\\)。 按照上面的背景,根据比值是否为 \\(1\\) 可以进行分类讨论: 三个圆的半径相同(样例的情形)。 若 \\(AB, AC\\) 的中垂线没有交(即平行),则无解。 否则这是唯一解。 判断平行:叉积是否为 \\(0\\)。 求两直线中垂线交点:设 \\(AB, AC\\) 中点分别为 \\(M, N,\\) 两垂直向量为 \\(\\vec{u}, \\vec{v},\\) 那么交点为 \\[M + \\dfrac{\\vec{v} \\times \\vec{OM} + \\vec{ON} \\times \\vec{v}}{\\vec{u} \\times \\vec{v}} \\cdot \\vec{u}\\] 半径两两不同。 按照比例求出两个阿圆,即: 对于 \\(A, B\\) 两圆,有两点与两圆心连线交于: \\[P = \\vec{OA} + \\vec{AB} \\times \\dfrac{rA}{rA + rB}, Q = \\vec{OA} + \\vec{AB} \\times \\dfrac{rB}{rA - rB}\\] 圆心 \\(M\\) 为 \\(P, Q\\) 中点,半径为 \\(P, Q\\) 距离的一半。\\(A, C\\) 同理。 两圆有交(\\(|R - r| < d < R + r\\)),离 \\(A\\) 更近的那个即为所求。 否则无解。 其中两个圆半径相同。 不妨令 \\(\\odot A\\) 与 \\(\\odot B\\) 半径相同。 求出 \\(AB\\) 中垂线,根据比例做出 \\(\\odot A, \\odot C\\) 的阿圆之后和另一个圆判交。 做法和上面类似。 展开参考代码 注:笔者的代码风格中有以下简单规则。 单字母 \\(\\verb|point|\\) 表示点(或者用希腊字母,如下文的 \\(\\tau\\)),而多字母表示向量。 \\(\\verb|l-|\\) 表示长度,\\(\\verb|-v|\\) 表示法向量(vertical)。 「圆」是点(向量)的子类,详细请见代码。 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183#include <bits/stdc++.h>using namespace std;using db = double;const db epsilon = 1e-6;const db pi = numbers::pi_v<db>;int sign(db x) { return x > epsilon ? 1 : x < -epsilon ? -1 : 0;}int cmp(db x, db y) { return sign(x - y);}int inmid(db x, db l, db r) { return sign(r - x) * sign(l - x) <= 0;}struct point { db x, y; point() = default; point(db x, db y): x(x), y(y) {} point operator + (const point & k) const { return { x + k.x, y + k.y }; } point operator - (const point & k) const { return { x - k.x, y - k.y }; } point operator * (db k) const { return { k * x, k * y }; } point operator / (db k) const { return { k / x, k / y }; } bool operator == (const point & k) const { return !cmp(x, k.x) && !cmp(y, k.y); } bool operator < (const point & k) const { int X = cmp(x, k.x); if (X == -1) { return true; } if (X == +1) { return false; } return !~cmp(y, k.y); } // counterclockwise point turn(db k) { return { x * cos(k) - y * cos(k), x * sin(k) + y * cos(k) }; } db abs() { return hypot(x, y); } db abs2() { return x * x + y * y; } db dis(point k) { return (( * this) - k).abs(); } point unit() { db w = this -> abs(); return { x / w, y / w }; } void in () { scanf("%lf%lf", & x, & y); } void out() { printf("%.11lf %.11lf", x, y); }};int inmid(point x, point l, point r) { return inmid(x.x, l.x, r.x) && inmid(x.y, l.y, r.y);}db cross(point a, point b) { return a.x * b.y - a.y * b.x;}db dot(point a, point b) { return a.x * b.x + a.y * b.y;}db rad(point a, point b) { return atan2(cross(a, b), dot(a, b));}struct circle: point { db r; void in () { point:: in (); scanf("%lf", & r); } void out() { point::out(); printf(" %.11f\\n", r); } int inside(point k) { return cmp(r, point::dis(k)); }};int main() { circle A, B, C; A.in(), B.in(), C.in(); if (!cmp(A.r, B.r) && !cmp(A.r, C.r)) { point AB = B - A, AC = C - A; if (!sign(cross(AB, AC))) { return 0x0; } point vAB { -AB.y, AB.x }, vAC { -AC.y, AC.x }; point M = A + AB * .5, N = A + AC * .5; db k = (cross(vAC, M) + cross(N, vAC)) / cross(vAB, vAC); (M + vAB * k).out(); } else if (!cmp(A.r, B.r) || !cmp(A.r, C.r) || !cmp(B.r, C.r)) { if (!cmp(A.r, C.r)) { swap(B, C); } if (!cmp(B.r, C.r)) { swap(A, C); } point AB { B - A }, vAB { -AB.y, AB.x }; point AC { C - A }, vAC { -AC.y, AC.x }; point M = A + AB * .5; point P = A + AC * (A.r / (A.r + C.r)); point Q = C + AC * (C.r / (A.r - C.r)); circle N { (P + Q) * .5, P.dis(Q) * .5 }; point MN = N - M; db lMN = MN.abs(), lvAB = vAB.abs(); db d = dot(vAB, MN) / lvAB; if (cmp(point(d, N.r).abs2(), lMN * lMN) < 0) { return 0x0; } double d0 = sqrt(abs(lMN * lMN - d * d)); double d1 = d - sqrt(abs(N.r * N.r - d0 * d0)); double d2 = d + sqrt(abs(N.r * N.r - d0 * d0)); point tau1 { M + vAB.unit() * d1 }; point tau2 { M + vAB.unit() * d2 }; (cmp(A.dis(tau1), A.dis(tau2)) < 0 ? tau1 : tau2).out(); } else { point AB = B - A, AC = C - A; circle M, N; { point P = A + AB * (A.r / (A.r + B.r)); point Q = B + AB * (B.r / (A.r - B.r)); M = { (P + Q) * .5, P.dis(Q) * .5 }; } { point P = A + AC * (A.r / (A.r + C.r)); point Q = C + AC * (C.r / (A.r - C.r)); N = { (P + Q) * .5, P.dis(Q) * .5 }; } db d = M.dis(N); if (cmp(abs(M.r - N.r), d) >= 0 || cmp(abs(M.r + N.r), d) <= 0) { return 0x0; } db theta = acos((d * d + M.r * M.r - N.r * N.r) / (d * M.r * 2)); db x = M.r * cos(theta), y = M.r * sin(theta); point MN = N - M, vMN { -MN.y, MN.x }; point S = M + MN.unit() * x; point tau1 { S + vMN.unit() * y }; point tau2 { S - vMN.unit() * y }; (cmp(A.dis(tau1), A.dis(tau2)) < 0 ? tau1 : tau2).out(); } return 0 ^ 0;}","link":"cf/2/c/"},{"title":"CF3A","text":"题意 国际象棋中的「王」每次可以向八联通的格子移动一格,问到达给定点的最短移动次数,并输出对应路径。 解答 直觉上尽可能走斜线会更近一些,这个直觉是正确的。 最少移动次数实际上为「切比雪夫距离」\\(\\max\\{|x_1 - x_2|, |y_1 - y_2|\\}\\)。 展开参考代码 一个简单的技巧是,把坐标两维分开考虑。可有效避免嵌套层数过多。 123456789101112131415161718192021#include <bits/stdc++.h>using namespace std;int main() { cin.tie(0), ios::sync_with_stdio(0); struct { char x, y; } a, b; cin >> a.x >> a.y >> b.x >> b.y; int step = max(abs(a.x - b.x), abs(a.y - b.y)); cout << step << "\\n"; for (int i = 0; i < step; ++i) { if (a.x < b.x) { a.x += 1; cout << "R"; } if (a.x > b.x) { a.x -= 1; cout << "L"; } if (a.y < b.y) { a.y += 1; cout << "U"; } if (a.y > b.y) { a.y -= 1; cout << "D"; } cout << "\\n"; } return 0 ^ 0;}","link":"cf/3/a/"},{"title":"CF3B","text":"题意 \\(01\\) 背包,但容量 \\(10 ^ 9\\),但物品体积 \\(\\in [1, 2]\\)。 解答 按照体积分为两类,不管是哪一类,在体积相同时一定是优先选取价值更大的物品。因而先对两类分别排序。 此处若感到难以为继,不妨返璞归真,枚举两者各自选取了多少。 对于其中一类,处理出前缀和来,这样就可以很好的与枚举相匹配。 于是瓶颈为排序,注意到物品价值 \\(p_i \\le 10 ^ 4\\),也许你可以尝试「基数排序」,不过 \\(\\verb|std::sort|\\) 已经足够快。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#include <bits/stdc++.h>using ll = long long;using namespace std;int main() { cin.tie(0), ios::sync_with_stdio(0); int n, v; cin >> n >> v; vector g(2, vector<pair<int, int>>{}); g[0].reserve(n); g[1].reserve(n); for (int i = 0, cost, gain; i < n; ++i) { cin >> cost >> gain; g[cost - 1].emplace_back(gain, i + 1); } int o = g[0].size(), t = g[1].size(); ranges::sort(g[0] | views::reverse); ranges::sort(g[1] | views::reverse); vector <ll> prefix(t + 1); for (int i = 0; i < t; ++i) { prefix[i + 1] = prefix[i] + g[1][i].first; } ll ans {}, sum {}; int s { -1 }, e { -1 }; for (int i = 0; i <= min(v, o); ++i) { sum += !i ? 0 : g[0][i - 1].first; int T = min(t, (v - i) / 2); if (sum + prefix[T] > ans) { ans = sum + prefix[T]; tie(s, e) = tuple { i, T }; } } cout << ans << "\\n"; for (auto[j, take]: { pair{0, s}, {1, e} }) { for (int i = 0; i < take; ++i) { cout << g[j][i].second << " \\n" [i + 1 == take]; } } return 0 ^ 0;}","link":"cf/3/b/"},{"title":"CF3C","text":"题意 判定给定的井字棋局属于何种局面。 解答 需要稍微分类讨论一下: 平局:两者落子数和为 \\(9\\)。 \\(A/B\\) 胜:三连珠。 该 \\(A\\) 落子:两者棋子数量相同。 该 \\(B\\) 落子:\\(A\\) 棋子多一颗。 非法: 两者皆赢。 \\(A\\) 落子数量太少 \\(A\\) 落子数量太多 \\(A\\) 赢了但 \\(B\\) 下了更多的棋 \\(B\\) 赢了但 \\(A\\) 下了更多的棋 展开参考代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768#include <bits/stdc++.h>using namespace std;int main() { cin.tie(0), ios::sync_with_stdio(0); vector<string> s(3); for (int i = 0; i < 3; ++i) { cin >> s[i]; } int c1 {}; for (string &x: s) { c1 += ranges::count(x, 'X'); } int c2 {}; for (string &x: s) { c2 += ranges::count(x, '0'); } auto get = [&](char ch) -> bool { bool win = false; for (int i = 0; i < 3; ++i) { // 横 win |= s[i] == string(3, ch); bool col = true; // 纵 for (int j = 0; j < 3; ++j) { col &= s[j][i] == ch; } win |= col; } if (s[1][1] == ch && ( // 主对角线 (s[0][0] == s[1][1] && s[0][0] == s[2][2]) || // 副对角线 (s[0][2] == s[1][1] && s[0][2] == s[2][0]) )) { win = true; } return win; }; bool win = get('X'), loz = get('0'); if (c1 < c2 || // A 落子数量少 c1 > c2 + 1 || // A 落子数量多 (win && loz) || // win-win (loz && (c1 == c2 + 1)) || // 惜败但脏棋 (win && (c1 == c2))) { // 豪胜但被脏 cout << "illegal\\n"; } else if (win) { cout << "the first player won\\n"; } else if (loz) { cout << "the second player won\\n"; } else if (c1 + c2 == 9) { cout << "draw\\n"; } else if (c1 == c2) { cout << "first\\n"; } else { cout << "second\\n"; } return 0 ^ 0;}","link":"cf/3/c/"},{"title":"CF3D","text":"题意 给定一个带问号的括号序列,将每个问号替换成左括号或右括号对应着不同的代价。问得到合法括号序列的最小代价。 或报告不可能。 解答 不妨率先安排所有括号,将 \\(\\verb|open|\\) 记为 \\(+\\),\\(\\verb|close|\\) 记为 \\(-\\)。 要使总代价最小,必然每个部分的代价都最小。当然前提是能够成功使得括号序列匹配。 从左向右扫描,记录当前括号的匹配情况,如果遇到右括号多于左括号,选择一个修改代价最小的进行修改。即 \\(\\verb|open| - \\verb|close|\\) 最小的项,随后此项不再被考虑,因此整个过程应当使用堆来维护。 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); string s; cin >> s; int n = s.size(); int open {}; ll ans {}; priority_queue<pair<int, int>> q; for (int i = 0; i < n; ++i) { if (s[i] == '(') { open += 1; } else if (s[i] == ')') { open -= 1; } else { int l, r; cin >> l >> r; q.emplace(-l + r, i); ans += r; s[i] = ')'; open -= 1; } if (open < 0) { if (q.empty()) { break; } auto [c, i] = q.top(); q.pop(); ans -= c; s[i] = '('; open += 2; } } if (open) { cout << "-1\\n"; } else { cout << ans << "\\n" << s << "\\n"; } return 0 ^ 0;}","link":"cf/3/d/"},{"title":"CF4A","text":"题意 能否把 🍉 分成两个正偶数? 解答 当且仅当 \\(2 \\mid\\) 🍉 且 🍉 \\(\\ge 4\\)。 展开参考代码 1234567891011#include <bits/stdc++.h>using namespace std;int main() { int x; cin >> x; cout << (x % 2 == 0 && x >= 4 ? "YES\\n" : "NO\\n"); return 0 ^ 0;}","link":"cf/4/a/"},{"title":"CF4B","text":"题意 给定总时间,是否能造一张复习计划表,使得每一天的学习时间都由一个区间限制。 解答 展开参考代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int n, s; cin >> n >> s; ll a{}, b{}; vector<int> x(n), y(n); for (int i : RI(0, n)) { cin >> x[i] >> y[i]; a += x[i]; b += y[i]; } if (s > b || s < a) { return cout << "NO\\n", int {}; } cout << "YES\\n"; s -= a; for (int i = 0; s && i < n; ++i) { if (int delta = y[i] - x[i]; s > delta) { s -= delta; x[i] = y[i]; } else { x[i] += s; s = 0; } } R::copy(x, ostream_iterator<int>(cout, " ")); return 0 ^ 0;}","link":"cf/4/b/"},{"title":"CF4C","text":"题意 给定若干用户名,如果该用户名并非首次出现,追加数字标记。否则输出 OK. 解答 记下出现次数。 展开参考代码 123456789101112131415161718192021222324#include <bits/stdc++.h>using namespace std;int main() { ios::sync_with_stdio(!cin.tie(0)); int n; cin >> n; map<string, int> occurrence; for (int i = 0; i < n; ++i) { string x; cin >> x; occurrence[x] += 1; if (occurrence[x] == 1) { cout << "OK\\n"; } else { cout << x << occurrence[x] - 1 << "\\n"; } } return 0 ^ 0;}","link":"cf/4/c/"},{"title":"CF4D","text":"题意 给定 \\((w, h)\\) 与 \\(n\\) 组 \\((w_i, h_i)\\)。求出 \\(w_i, h_i\\) 都严格大于 \\(w, h\\) 的严格上升子序列、及其长度。 解答 首先把那些比 \\((w, h)\\) 还要小的元素都筛掉,剩下的问题是一个二维偏序问题。其实 \\(\\sout{\\rm LIS}\\) 问题也就是 \\(\\sout{i \\lt j \\land a_i \\lt a_j}\\) 而已,只不过不能选择重复的点情况下第一维的限制很好满足罢了。 处理二维偏序问题的核心思路是:固定某一维,枚举另一维。 只不过大多时间都希望枚举的复杂度低一些。如果具有单调性,既可以直接二分,也可以在值域上开一个数状数组。 本题属于 \\((h_i, w_i) \\prec (h_j, w_j)\\) 且 \\(h_i \\lt h_j \\land w_i \\lt w_j\\) 且两维都可重的情况。需要对第二维进行逆序排序,随后执行 \\(\\rm LIS\\) 的步骤。 另外,输出路径,需要记录当前最长子序列长度发生在哪一个位置。 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061#include <bits/stdc++.h>using namespace std;#define R ranges#define V views#define RI R::iota_viewint main() { int n; cin >> n; int w, h; cin >> w >> h; vector a(0, tuple{0, 0, 0}); a.reserve(n); for (int i : RI(0, n)) { int wi, hi; cin >> wi >> hi; if (wi > w && hi > h) { a.emplace_back(wi, hi, i + 1); } } // 排序 R::sort(a, [&](auto a, auto b) { auto [wa, ha, _a] = a; auto [wb, hb, _b] = b; return wa == wb ? ha > hb : wa < wb; }); n = a.size(); vector<int> dp(n + 1, 1E9); vector<int> f(n, 0); int top = 0; for (int i : RI(0, n)) { auto pos = lower_bound(dp.begin(), dp.begin() + top + 1, get<1>(a[i])); top = max(top, f[i] = pos - dp.begin()); *pos = get<1>(a[i]); } vector ans(0, 0); ans.reserve(n); // 倒着跑 for (int i : RI(0, n) | V::reverse) { if (f[i] == top) { ans.push_back(get<2>(a[i])); top -= 1; } } cout << ans.size() << "\\n"; R::copy(ans | V::reverse, ostream_iterator<int>{cout, " "}); return 0 ^ 0;}","link":"cf/4/d/"},{"title":"CF5A","text":"题意 为聊天系统实现以下三个功能: 添加用户 删除用户 向所有用户发送一条消息,这贡献用户数量 \\(\\times\\) 信息长度的流量。 输出总流量。 解答 因为数据保证不会添加已经添加过的用户、不会删除不存在的用户。只需要记下当前的用户总数。 展开参考代码 123456789101112131415161718192021222324#include <bits/stdc++.h>using namespace std;int main() { ios::sync_with_stdio(!cin.tie(0)); string s; int ans{}, online{}; while (getline(cin, s)) { if (s[0] == '+') { online += 1; } else if (s[0] == '-') { online -= 1; } else { ans += online * (s.size() - s.find(":") - 1); } } cout << ans << "\\n"; return 0;}","link":"cf/5/a/"},{"title":"CF5B","text":"题意 将给定的若干字符串居中排版,如果无法完美居中。依次向左、向右微调一个字符。 解答 记下最长的一行,随后需要记下来是第几次无法完美居中。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536#include <bits/stdc++.h>using namespace std;int main() { ios::sync_with_stdio(!cin.tie(0)); string s; vector<string> x; size_t m{}; while (getline(cin, s)) { x.push_back(s); m = max(m, s.size()); } cout << string(m + 2, '*') << "\\n"; bool occur = true; for (string& si : x) { int s = m - si.size(); occur ^= s & 1; int offset = occur * (s & 1); cout << '*'; cout << string((s + offset) / 2, ' ') << si << string((s + 1 - offset) / 2, ' '); cout << "*\\n"; } cout << string(m + 2, '*') << "\\n"; return 0;}","link":"cf/5/b/"},{"title":"CF5C","text":"题意 计算出给定括号序列的最长合法括号序列长度以及数量。 特殊的,如果没有合法序列,输出 \\(\\verb|"0 1"|\\)。 解答 用栈进行括号匹配,中途记下所有合法的位置。最后扫描一次,期间维护两个答案。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445#include <bits/stdc++.h>using namespace std;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); string s; cin >> s; int n = s.size(); vector<int> stk; stk.reserve(n); vector<bool> vis(n); for (int i : RI(0, n)) { if (char si{s[i]}; si == '(') { stk.push_back(i); } else if (stk.size()) { vis[i] = vis[stk.back()] = true; stk.pop_back(); } } int m{}, occur{1}, cnt{}; for (int i : RI(0, n)) { cnt = vis[i] ? cnt + 1 : 0; if (m != 0 && cnt == m) { occur += 1; } else if (m < cnt) { m = cnt; occur = 1; } } cout << m << " " << occur << "\\n"; return 0;}","link":"cf/5/c/"},{"title":"CF5D","text":"题意 车辆始终以 \\(a\\) 的加速度行驶,最大速度为 \\(v\\)。路程总长 \\(l\\),在 \\(d\\) 位置上有一个限速点,限制 该点速度 为 \\(w\\)。 求通过此路段的最短时间。 解答 高中物理题。可以使用画图的方式便于理解,但总的来说需要考虑许多情况: 一直加速,即使到达限速点,速度也依然在可控制的范围内。那么一直加速过去就可以了。 否则,需要在检查点之前减速到 \\(w\\)。 如果在此过程中甚至没有到最大速度(或瞬间开始减速),意味着关于速度的图像形如 \\(\\boxed{\\diagup\\diagdown}\\)。 否则有一段以最大速度匀速行驶的阶段。即 \\(\\boxed{\\diagup\\overline{v \\rightarrow v}\\diagdown}\\)。 最后一直加速到终点。 🇨🇳 以上内容对于中国理科生来说十分容易,具体每一步的解析如下: 讲解一:计算时间 给定初速度 \\(v_0\\),限速 \\(v_m\\),加速度为 \\(a\\),走过 \\(l\\) 长路的最短时间。 给定双点速度,使用 \\(v_t^2 - v_0^2 = 2ax\\) 来求得位移 \\(x\\)。 若位移比 \\(l\\) 要大(或等),那么直接冲过去就好了。时间花费为方程 \\(\\dfrac{1}{2}at^2 + v_0t = l\\) 的实数解 \\[\\color{red}\\boxed{\\dfrac{-v_0 + \\sqrt{v_0^2 + 2al}}{a}}\\] 否则先加速到 \\(v_m\\) 随后一直匀速。即 \\[\\color{red}\\boxed{t = \\dfrac{v_m - v_0}{a} + \\dfrac{l - x}{v_m}}\\] 1234567auto T = [&](db v0, db vm, db a, db l) -> db { if (db x = (vm + v0) * (vm - v0) / (2 * a); x - l > -epsilon) { return (sqrt(v0 * v0 + 2 * a * l) - v0) / a; } else { return (vm - v0) / a + (l - x) / vm; }}; 讲解二:情形 2-1 的分界点 设从速度 \\(v_t\\) 时开始减速。使用两次 \\(v_t^2 - v_0^2 = 2ax\\) 得: \\[\\dfrac{v_t^2}{2a} + \\dfrac{v_t^2 - w^2}{2a} = d\\] 于是: \\[\\color{red}\\boxed{v_t = at_0 = \\sqrt{\\dfrac{2ad + w ^ 2}{2}}}\\] 如果 \\(v_t \\le v\\),说明中间不存在一段匀速行驶的路段,根据对称性,可得 \\(t_1 = 2t_0 - \\dfrac{w}{a}\\)。图像如下: 对称性 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637#include <bits/stdc++.h>using namespace std;using db = double;static constexpr db epsilon = 1E-6;int main() { ios::sync_with_stdio(!cin.tie(0)); cout << fixed << setprecision(10); auto T = [&](db v0, db vm, db a, db l) -> db { if (db x = (vm + v0) * (vm - v0) / (2 * a); x - l > -epsilon) { return (sqrt(v0 * v0 + 2 * a * l) - v0) / a; } else { return (vm - v0) / a + (l - x) / vm; } }; db a, v, l, d, w; cin >> a >> v >> l >> d >> w; if (db s = w * w / (2 * a); s - d > -epsilon || w - v > -epsilon) { cout << T(0, v, a, l) << "\\n"; } else { db t1{}; if (db t0 = sqrt((2 * a * d + w * w) / (2 * a * a)); t0 * a - v < epsilon) { t1 = 2 * t0 - w / a; } else { db x1 = v * v / (2 * a); db x2 = (v + w) * (v - w) / (2 * a); t1 = v / a + (v - w) / a + (d - x1 - x2) / v; } cout << t1 + T(w, v, a, l - d) << "\\n"; } return 0;}","link":"cf/5/d/"},{"title":"CF5E","text":"题意 给定一个 \\(n\\) 个点的环,计算对子 \\((i, j), i \\ne j\\) 的数量。其中 \\(i, j\\) 相连的两条弧中任意一条没有比他们更高的点。 解答 遇到环的第一想法应该是破环成链。题目希望两个点中间没有比他们高的点,最高的点一定是答案的一部分,因此不妨从这里破开。接着就可以考虑在链上怎么解决这个问题了。 连接任意两点,链不合法将会在中间某点断开,使得某段是合法的解。对于固定的右端点来说,这个断开的点是左边最近的、比他大的点。 这是单调栈的基本问题,即 \\(\\rm NGE\\)(Next Greater Element)。 展开参考代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int n; cin >> n; vector a(n, 0); int pos{}; for (int i : RI(0, n)) { cin >> a[i]; if (a[i] > a[pos]) { pos = i; } } R::rotate(a, a.begin() + pos); // ++top, top--, top 长度上完胜 // push_back pop_back back int top{}; vector stk(n + 1, 0); vector cnt(n + 1, 0); // 次数 // n - 1 对相邻 ll ans{n - 1}; for (int i : RI(1, n)) { // 枚举右端点 // 匹配更大的左端点 while (top && stk[top] < a[i]) { ans += cnt[top--]; } // 严格高于 -- 要判重 if (stk[top] != a[i]) { stk[++top] = a[i]; cnt[top] = 0; } ans += cnt[top]++; } // 最后一次: 加入最大值 while (top > 1) { ans += cnt[top--]; } cout << ans << "\\n"; return 0;}","link":"cf/5/e/"},{"title":"CF652D","text":"题意 给定若干线段,问每条线段包含多少条其他线段,保证端点不重合。 \\(1 \\le n \\le 2 \\times 10 ^ 5\\) \\(- 10 ^ 9 \\le l_i \\lt r_i \\le 10 ^ 9\\) 解答 线段之间的包含关系可以描述为 \\[ (l_i, r_i) \\prec (l_j, r_j) \\xlongequal{ 等价于 } l_i \\gt l_j \\land r_i \\lt r_j \\] 这样问题就转化为二维数点了,数据保证不重合,自然也不需要考虑排序。 CF652D.cpp >folded1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859// g++ CF652D.cpp -std=c++17 -Os -DLOCAL -o CF652D#include <bits/stdc++.h>using namespace std;static constexpr int maxn = 200010;array<int, 3> a[maxn];int c[maxn], tot;int n;int ans[maxn];int tr[maxn];int qry(int i) { int ans{}; for (; i; i -= i & -i) { ans += tr[i]; } return ans;}void mdf(int i, int x) { for (; i <= n; i += i & -i) { tr[i] += x; }}int main() { ios::sync_with_stdio(!cin.tie(0)); cin >> n; for (int i = 1; i <= n; ++i) { cin >> a[i][0] >> a[i][1]; a[i][2] = i; c[i] = a[i][1]; } sort(a + 1, a + 1 + n, [](auto &a, auto &b) { if (a[0] == b[0]) { return a[1] < b[1]; } return a[0] > b[0]; }); sort(c + 1, c + 1 + n); tot = unique(c + 1, c + 1 + n) - (c + 1); for (int i = 1; i <= n; ++i) { int l = lower_bound(c + 1, c + 1 + tot, a[i][1]) - c; ans[a[i][2]] += qry(l); mdf(l, 1); } for (int i = 1; i <= n; ++i) { cout << ans[i] << "\\n"; } return 0 ^ 0;}","link":"cf/652/d/"},{"title":"EC2017-XiAn-G","text":"题意 多次询问某区间内所有区间的异或和。 \\(0 \\le A_i \\le 10 ^ 6\\) \\(1 \\le n, q \\le 10 ^ 5\\) https://nanti.jisuanke.com/t/A1613 用前缀和解答 按位考虑,对于每一位来说,其贡献 \\(s_i\\) 为 \\(1\\) 的位。于是答案为「前缀异或和数组中两两异或再求和的值」,也就是只有 \\(01\\) 之间才有贡献,于是每一段的贡献为: \\[ 2^i \\times \\operatorname{cnt}_1 \\times \\operatorname{cnt}_0 \\] 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849#include <bits/stdc++.h>using ll = long long; // <+>const int maxn = 10 + 100000, bits = 21, mod = 1000000007;int s[bits][maxn];int main() { std::cin.tie(nullptr) -> sync_with_stdio(false); int tests; std::cin >> tests; while (tests--) { int n, q; std::cin >> n >> q; for (int i = 1, x; i <= n; ++i) { std::cin >> x; for (int j = 0; j < bits; ++j) { s[j][i] = s[j][i - 1] ^ (x >> j & 1); } } for (int j = 0; j < bits; ++j) { for (int i = 1; i <= n; ++i) { s[j][i] += s[j][i - 1]; } } while (q--) { int l, r; std::cin >> l >> r; l -= 1; ll ans {}; for (int j = 0; j < bits; ++j) { int o = s[j][r] - s[j][l - 1], z = (r - l + 1) - o; ans = (ans + 1 LL * o * z % mod * (1 LL << j) % mod) % mod; } std::cout << ans << "\\n"; } } return 0 ^ 0;}","link":"ec/2017/xian/g/"},{"title":"EC2021-XiAn-A","text":"题意 一颗子树因为邻接点遍历顺序不同存在多个字典序,现问每个点在所有 \\(\\rm DFS\\) 序中的编号中最小、最大是多少。 https://codeforces.com/gym/103861/problem/A 解答 最好的情况是从根直接冲向当前节点 \\(i\\),花费为深度 \\(\\operatorname{depth}_i\\)。 否则,遍历完其他节点后只剩下 \\(i\\) 这棵子树没有遍历,编号为 \\(n - \\operatorname{size}_i + 1\\)。使用一次 \\(\\rm DFS\\) 即可求出。 2021ECF-A.cpp >folded1234567891011121314151617181920212223242526272829303132333435363738394041424344#include <bits/stdc++.h>void solve() { int n; ::std::cin >> n; ::std::vector g(n, ::std::vector(0, 0)); for (int i = 1, x, y; i < n; ++i) { ::std::cin >> x >> y; x -= 1, y -= 1; g[x].push_back(y); g[y].push_back(x); } ::std::vector dep(n, 0), size(n, 0); dep[0] = 1; ::std::function<void(int, int)> dfs = [&](int u, int p) -> void { size[u] = 1; for (int v : g[u]) if (v != p) { dep[v] = dep[u] + 1; dfs(v, u); size[u] += size[v]; } }; dfs(0, -1); for (int i = 0; i < n; ++i) { ::std::cout << dep[i] << " " << n + 1 - size[i] << "\\n"; }}int main() { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int tests; ::std::cin >> tests; while (tests --) { solve(); } return 0;}","link":"ec/2021/xian/a/"},{"title":"EC2021-XiAn-E","text":"题意 三个人 \\(A \\rightarrow B \\rightarrow P\\) 明牌打牌,\\(A\\) 先手。\\(P\\) 只剩下一张牌。\\(A\\) 希望 \\(P\\) 赢,\\(B\\) 希望 \\(P\\) 输,问最佳策略、每次只能出一张牌情况下 \\(P\\) 是否会赢。 https://codeforces.com/gym/103861/problem/E 解答 有趣的是,打牌赢下的规则是先打完手中的牌。并且需要严格大于上家的牌才能打出来。 所有的讨论: 12345678910111213// fp = for P = 比 B 大但是没 P 大的一系列牌int fp = *::std::prev(::std::ranges::lower_bound(a, p));if (n == 1 or a0 >= p or b0 >= p) LOZ // A 剩一张牌,或者 A / B 最小的牌都比 P 大else if (m == 1 and b0 <= fp) WIN // B 剩一张牌,并且这张牌比 fp 小else if (m >= 2 and b[1] < p) WIN // B 对上 fp 之后打不出比 P 更大的牌else if (n > 3 and a[1] < p and b0 <= fp and an > bm) WIN // 与 B 对过一轮之后到 A,此时至少还剩三张牌的话// A 可以把最大的牌打出来抢到机会打出 fpelse LOZ 2021ECF-E.cpp >folded12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758#include <bits/stdc++.h>void solve() { int n, m; ::std::cin >> n >> m; // lambda 无参数不用写 () auto helper = [] -> int { char ch; ::std::cin >> ch; ::std::cin.ignore(); switch (ch) { case 'A': return 14; case 'K': return 13; case 'Q': return 12; case 'J': return 11; case 'T': return 10; default : return ch - '0'; } }; ::std::vector a(n, 0), b(m, 0); ::std::ranges::generate(a, helper); ::std::ranges::generate(b, helper); int p = helper(); ::std::ranges::sort(a); ::std::ranges::sort(b);#define WIN ::std::cout << "Pang\\n";#define LOZ ::std::cout << "Shou\\n"; int fp = *::std::prev(::std::ranges::lower_bound(a, p)); int a0 = a.front(), b0 = b.front(); int an = a.back(), bm = b.back(); if (n == 1 or a0 >= p or b0 >= p) LOZ else if (m == 1 and b0 <= fp) WIN else if (m >= 2 and b[1] < p) WIN else if (n > 3 and a[1] < p and b0 <= fp and an > bm) WIN else LOZ#undef WIN#undef LOZ}int main() { ::std::ios::sync_with_stdio(not ::std::cin.tie(nullptr)); int tests; ::std::cin >> tests; while (tests--) { solve(); } return 0;}","link":"ec/2021/xian/e/"},{"title":"EC2021-XiAn-I","text":"题意 计算满足 \\(A_i \\times A_j < A_i + A_j\\;(i \\lt j)\\) 的对子 \\((i, j)\\) 数量。 https://codeforces.com/gym/103861/problem/I 解答 稍作变形可得 \\((A_i - 1)(A_j - 1) < 1\\): 为便于说明,计算 \\(\\{A - 1\\}\\) 中的正负零数量为 \\(P, N, Z\\). \\(\\mathrm{LHS} \\lt 0\\): \\(PN.\\) \\(\\mathrm{LHS} = 0\\): 即至少有一个 \\(0\\),答案分为: 一个 \\(0\\): \\((n - 1)Z\\) 两个 \\(0\\): 上面的情形已经包含了。因此需要减去多出来的贡献: \\(\\displaystyle\\binom{Z}{2}\\) 综上, \\(nZ - Z - \\displaystyle\\binom{Z}{2} = nZ - \\displaystyle\\binom{Z + 1}{2}\\) 2021ECF-I.cpp >folded12345678910111213141516171819202122232425262728293031#include <bits/stdc++.h>void solve() { int n; ::std::cin >> n; long long P { }, N { }, Z { }; ::std::vector a(n, 0); for (int &i : a) { ::std::cin >> i; i -= 1; P += i > 0; N += i < 0; Z += i == 0; } ::std::cout << P * N + Z * n - Z * (Z + 1) / 2 << "\\n";}int main() { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int tests; ::std::cin >> tests; while (tests --) { solve(); } return 0;}","link":"ec/2021/xian/i/"},{"title":"EC2021-XiAn-J","text":"题意 给定一张带有点权的无向图,除了你所在的 \\(1\\) 号点外,每个点上包含一个战力为 \\(l_i\\) 的怪物。 在某一天,你可以对与「安全地带」联通的一个点上的怪兽决斗,如果你赢下将获得 \\(A\\) 的战力值。 无论你是否发起进攻,每一天最后,未被击杀的怪兽都将获得 \\(B\\) 的战力增幅。 你的目的是用最短时间击杀位于点 \\(n\\) 的怪兽。 https://codeforces.com/gym/103861/problem/J 解答 需要对 \\(A, B\\) 的大小进行讨论: 如果 \\(A \\le B\\) 意味着怪兽变强速度比你快,此时需要在尽量短的时间内击杀怪兽,因为如果 \\(x\\) 天无法行动,\\(x + 1\\) 天也没办法。因此走最短路,边权为 \\(1\\) 的最短路使用 \\(\\rm BFS\\) 即可。 否则 \\(A > B\\),这意味着也许有些时候需要通过打一些怪兽来增加战力从而接近 \\(n\\),只要能打下去就一直选择当前最弱的怪兽来打。维护一个堆,记下那些「当前这一圈能打掉的所有怪」,每次都先尝试把这些怪里面能打掉的都先打完,以免有一个分支一直打得过但突然遇到一个很强的怪,此时再回去连当时可以打过的也不打过的尴尬局面出现。因此额外记下下一圈的怪兽,等现在的都打完了再拓展。 2021ECF-J.cpp >folded1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677#include <bits/stdc++.h>void solve() { int n, m; long long a, b; ::std::cin >> n >> m >> a >> b; ::std::vector g(n, ::std::vector(0, 0)); while (m --) { int u, v; ::std::cin >> u >> v; u -= 1, v -= 1; g[u] . push_back(v); g[v] . push_back(u); } ::std::vector l(n, 0); for (int &i : l) { ::std::cin >> i; i += b; } l[0] -= b; if (a <= b) { ::std::vector dist(n, -1); ::std::queue<int> q; q.push( dist[0] = 0 ); while (q.size()) { int u = q.front(); q.pop(); for (int v : g[u]) if (dist[v] == -1 && l[0] > l[v] + (b - a) * dist[u]) { q.push(v); dist[v] = dist[u] + 1; } } ::std::cout << dist.back() << "\\n"; } else { ::std::priority_queue< ::std::pair<int, int> > q; ::std::vector visited(n, 0); visited[0] = 1; for (int i : g[0]) if (!visited[i]) { visited[i] = 1; q.emplace(-l[i], i); } // cnt 表示打怪(出队)次数,t 表示经过了若干天 for (int t = 0, cnt = 0; t < n and t <= cnt; ++t) { ::std::vector next(0, ::std::pair{0, 0}); while (q.size() and l[0] > - q.top().first - (a - b) * t) { auto [h, u] = q.top(); q.pop(); cnt += 1; if (u + 1 == n) { ::std::cout << 1 + t << "\\n"; return void(); } for (int v : g[u]) if (!visited[v]) { next . emplace_back(-l[v], v); visited[v] = 1; } } for (auto &x : next) { q.push(x); } } ::std::cout << "-1\\n"; }}int main() { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int tests; ::std::cin >> tests; while (tests --) { solve(); } return 0;}","link":"ec/2021/xian/j/"},{"title":"EC2021-XiAn-L","text":"题意 在树状数组上已经有许多点有数字了,问在空树状数组上至少经过多少次单点加才能变为给定的结果。 https://codeforces.com/gym/103861/problem/L 解答 真的好难说明白啊,写了一个又一个版本全删了。 总之就是,考虑父亲节点的修改次数。如果次数为 \\(1\\) 而需要 \\(0\\) 就需要对其进行一次抵消(即加负数);否则如果次数为 \\(0\\) 而需要 \\(1\\) 就需要对其加一次正数。次数更多都可以通过加若干次正数或负数从而间接变为 \\(0\\)。假如要操作 \\(\\{4, 6, 7\\}\\) 就可以加两次正数一次负数从而抵消掉对于 \\(8\\) 的影响。结合上图十分直观。 2021ECF-L.cpp >folded123456789101112131415161718192021222324252627282930313233343536373839404142#include <bits/stdc++.h>void solve() { int n; ::std::string s; ::std::cin >> n >> s; for (auto &ch : s) { ch -= '0'; } s = '$' + s; auto lowbit = [](int x) -> int { return x & (-x); }; ::std::vector C(1 + n, 0); for (int i = 1; i <= n; ++i) if (s[i]) { if (int x = i + lowbit(i); x <= n) { C[x] += 1; } } int ans { }; for (int i = 1; i <= n; ++i) { if (s[i]) { ans += C[i] == 0; } else { ans += C[i] == 1; } } ::std::cout << ans << "\\n";}int main() { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int tests; ::std::cin >> tests; while (tests --) { solve(); } return 0;}","link":"ec/2021/xian/l/"},{"title":"NC11255E","text":"题意 \\(n\\) 个节点的树,给定边权为两点点权异或和 \\(w_{u, v} = w_u \\oplus w_v\\)。每个点权的取值范围 \\(l_i \\le w_i \\le r_i\\),求满足条件的 \\(w_i\\) 的数量。 \\(n \\in [1, 10 ^ 5], l_i, r_i \\in [1, 2 ^ {30}]\\) https://ac.nowcoder.com/acm/contest/11255/E 解答 确定任一点即可确定整棵树,不妨以根为中心。如果现在根节点是 \\(x \\in [l_0, r_0]\\),需要考虑对于其他节点来说是否有: \\[ w_i \\in [l_i, r_i] \\overset{?}{\\Rightarrow} w_i \\oplus x \\in [l_i, r_i] \\] 等号成立,当且仅当 \\([l, r]\\) 中包含 \\(2 ^ k\\) 个数并且低 \\(k\\) 位包含 \\(0 \\sim 2 ^ k - 1\\) 1。 「低位包含全部的数」这样的条件,即可以转换为一段连续的区间从而在值域开线段树来解决,也同时意味着这些点是 Trie 的一棵子树。 用 Trie 标记出不合法的区间,再计算该子树的贡献即可(相当于对若干合法区间求交)。更详细的部分可见代码注释。 展开参考代码 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113#include <bits/stdc++.h>using ll = long long;using namespace std;// 等价于 const int N; 字典树要开 bits * nodesenum { N = 30 * 100010};int n;int head[N], cnt, u, v, w;struct { int next, to, w;}edges[N << 1];void addEdge(int u, int v, int w) { edges[++cnt] = { head[u], v, w }; head[u] = cnt;}// Trie 部分. T tag {0/1/2} 表示 {2/1/0} 个儿子合法int son[N][2], tot;int T[N];int newNode() { ++tot; T[tot] = son[tot][0] = son[tot][1] = 0; return tot;}int trie(int u, int b, int l, int r, int w) { // 已经不合法/这颗子树覆盖 2 ^ bits 个树 // 即 r - l = 2 ^ (b + 1) - 1 下同 if (T[u] == 2 || r - l == ~-(1 << -~b)) { return T[u]; } for (int v: { 0, 1 }) { if (!son[u][v]) { son[u][v] = newNode(); } } // l, r 第 b 位相同 if (int m = 1 << b; (l & m) == (r & m)) { // 当前的权与 l 第 b 位相同 // 不相同的那一部分 l, r 也管不了直接标记为无用 bool st = (l & m) ^ (w & m); T[son[u][!st]] = 2; // m = 2 ^ b. %m 其实就是 & ( m - 1 ) T[u] = trie(son[u][st], b - 1, l % m, r % m, w) == 2 ? 2 : 1; } else { bool st = w & m; // b 位是 1/0 时子树的情况 int lft = trie(son[u][st], b - 1, l % m, m - 1, w); int rgt = trie(son[u][!st], b - 1, 0, r % m, w); if (lft == 2 && rgt == 2) { T[u] = 2; } else if (lft != 2 || rgt != 2) { T[u] = 1; } } return T[u];}int L[N], R[N];void dfs(int u, int p, int w) { trie(1, 29, L[u], R[u], w); for (int i = head[u]; i; i = edges[i].next) { if (int v = edges[i].to; v != p) { dfs(v, u, w ^ edges[i].w); } }}ll ans {};void getAns(int u, int b) { if (!T[u]) { // 整棵树都可以用 ans += 1 LL << -~b; } else if (T[u] == 1) { // 只有一边能用 for (int v: { 0, 1 }) { if (T[son[u][v]] != 2) { getAns(son[u][v], b - 1); } } }}int main() { scanf("%d", & n); for (int i = 1; i <= n; ++i) { scanf("%d%d", L + i, R + i); } for (int i = 1; i < n; ++i) { scanf("%d%d%d", & u, & v, & w); addEdge(u, v, w); addEdge(v, u, w); } newNode(); dfs(1, 0, 0); getAns(1, 29); printf("%lld", ans); return 0 ^ 0;} 这里隐含了高位均相同的前提,对于题目中的区间来说,这显然是成立的。↩︎","link":"nc/11255/e/"},{"title":"P1823","text":"题意 和 CF5E 十分类似,但没有「环」这一条件。 https://www.luogu.com.cn/problem/P1823 解答 如果按照上面的做法尝试解决此题,会发现大多时候都数多了。TODO: 2022-08-19 08:59:06 Patricky 原因未知。 12345678910111213141516int top{};vector stk(n + 1, 0);vector cnt(n + 1, 0);ll ans{};for (int i : RI(0, n)) { while (top && stk[top] < a[i]) { ans += cnt[top--]; } ans += !!top; if (stk[top] != a[i]) { stk[++top] = a[i]; cnt[top] = 0; } ans += cnt[top]++;} 记录下来相同元素的出现次数然后统计。 展开参考代码 12345678910111213141516171819202122232425262728293031323334353637383940414243#include <bits/stdc++.h>using namespace std;using ll = long long;#define R ranges#define RI R::iota_viewint main() { ios::sync_with_stdio(!cin.tie(0)); int n; cin >> n; vector a(n, 0); for (int i : RI(0, n)) { cin >> a[i]; } int top{}; // 存储元素以及出现次数 vector stk(n + 1, array<int, 2>{}); ll ans{}; for (int i : RI(0, n)) { int cnt{}; while (top && stk[top][0] <= a[i]) { if (stk[top][0] == a[i]) { cnt = stk[top][1]; } ans += stk[top--][1]; } // 相邻元素 ans += !!top; stk[++top] = {a[i], cnt + 1}; } cout << ans << "\\n"; return 0;}","link":"p/1823/"},{"title":"P2163","text":"题意 这是二维数点的模板题。 多次查询平面上某矩形内的点数,一个坐标上可能有多个点。 点数 \\(5 \\times 10 ^ 5\\) 大小 \\(10 ^ 7\\) 询问次数 \\(5 \\times 10 ^ 5\\) https://www.luogu.com.cn/problem/P2163 https://darkbzoj.cc/problem/1935 解答 根据二维前缀和的启示,只需要求得从 \\((0, 0)\\) 到 \\((x, y)\\) 这个矩形的信息,就可以 \\(\\mathcal O(1)\\) 计算出答案。 具体而言不出来了。😅 博客站居然渲染不了下面这段代码,反正就是容斥一下。>folded1234567891011$$\\begin{aligned}&\\boxed{S_{{x_1, y_1} \\rightarrow {x_2, y_2}}}\\\\=\\quad&S_{0, 0 \\rightarrow {x_2, y_2}}\\quad&-\\quad&S_{0, 0 \\rightarrow {x_2, y_1 - 1}}\\\\-\\quad&S_{0, 0 \\rightarrow {x_1 - 1, y_2}}\\quad&+\\quad&S_{0, 0 \\rightarrow {x_1 - 1, y_1 - 1}}\\end{aligned}$$ 朴素的二维前缀和花费 \\(\\mathcal O(n ^ 2)\\),当然是不行的,重新整理问题,即求: \\[ \\sum_{i=0}^{x} \\sum_{j=0}^y S_{i, j} \\xlongequal{等价于}\\sum_{i=0}^{x} \\sum_{j=0}^y [(i, j) \\prec (x, y)], \\mathrm{\\;where\\;} i \\le x \\land j \\le y \\] 这样的点对,可以用先前提到的二维偏序解决。尽管坐标上可能会有点重复,但并不影响。将询问离线下来之后和原数据都按照横坐标排序。接着离散化,最后用树状数组统计答案: 12345678for (int i = 1, j = 1; i <= t; ++i) { // 把小于当前询问横坐标的点(的纵坐标)加到树状数组里 while (j <= n && a[j][0] <= q[i][0]) { mdf(a[j++][1], 1); } // 对于每个询问 求出前缀和 利用容斥标记符号 ans[q[i][2]] += q[i][3] * qry(q[i][1]);} 下面的代码吸吸氧才能过 P2163.cpp >folded123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081// g++11 P2163.cpp -o P2163 -std=c++11 -O2#include <bits/stdc++.h>using namespace std;static constexpr int maxn = 5000005;// x, yarray<int, 2> a[maxn];// x, y, id, weightarray<int, 4> q[maxn << 2];int n, m, t, ans[maxn];int c[maxn * 5], tot;int tr[maxn];int qry(int i) { int ans{}; for (; i; i -= i & -i) { ans += tr[i]; } return ans;}void mdf(int i, int v) { for (; i <= n; i += i & -i) { tr[i] += v; }}int main() { ios::sync_with_stdio(!cin.tie(0)); cin >> n >> m; for (int i = 1; i <= n; ++i) { cin >> a[i][0] >> a[i][1]; a[i][0] += 1, a[i][1] += 1; c[++tot] = a[i][1]; } for (int i = 1; i <= m; ++i) { array<int, 4> qi{}; for (int j = 0; j < 4; ++j) { cin >> qi[j]; qi[j] += 1; } // 询问左上角的点记的是 y_1 - 1 c[++tot] = qi[1] - 1; c[++tot] = qi[3]; q[++t] = {qi[0] - 1, qi[1] - 1, i, 1}; // (x1 - 1, y1 - 1) q[++t] = {qi[0] - 1, qi[3], i, -1}; // (x1 - 1, y2) q[++t] = {qi[2], qi[1] - 1, i, -1}; // (x2, y1 - 1) q[++t] = {qi[2], qi[3], i, 1}; // (x2, y2) } sort(c + 1, c + 1 + tot); tot = unique(c + 1, c + 1 + tot) - (c + 1); for (int i = 1; i <= n; ++i) { a[i][1] = lower_bound(c + 1, c + 1 + tot, a[i][1]) - c; } for (int i = 1; i <= t; ++i) { q[i][1] = lower_bound(c + 1, c + 1 + tot, q[i][1]) - c; } sort(a + 1, a + 1 + n); sort(q + 1, q + 1 + t); for (int i = 1, j = 1; i <= t; ++i) { while (j <= n && a[j][0] <= q[i][0]) { mdf(a[j++][1], 1); } ans[q[i][2]] += q[i][3] * qry(q[i][1]); } for (int i = 1; i <= m; ++i) { cout << ans[i] << "\\n"; } return 0 ^ 0;}","link":"p/2163/"},{"title":"VJ1194","text":"题意 用 \\(1 \\times 2\\) 的骨牌填满 \\(M \\times N\\) 的矩阵方案数,其中 \\(M \\le 5.\\) \\(N \\in [1, 10 ^ 9]\\) https://vijos.org/p/1194 解答1 假设已经放置了若干(或无)骨牌,从 \\(M\\) 这一维来考虑,这些骨牌的摆放方式可能有 \\(2 ^ M\\) 种。再加入若干牌,这一行有可能被填满吗? 用 \\(i, j\\) 表示当前行状态和可达的下行状态。有两条限制: \\(i \\operatorname{or} j = 2 ^ M -1.\\) \\(i \\& j\\) 为若干(或无)\\(1 \\times 2\\) 骨牌。具体来说: \\[ i \\& j \\in \\rm\\{ 0B00000, 0B00011, 0B00110, 0B01100, 0B01111, 0B11000, 0B11011, 0B11110 \\} \\] 最终希望每行都转化为全 \\(1\\),这实际上是在求从 \\(\\verb|1..11|\\) 到 \\(\\verb|1..11|\\) 恰好经过 \\(N\\) 步的路径数量。于是,原问题转化为一个经典的问题: \\(\\bm{Floyd}\\) 算法中的邻接矩阵自乘 \\(k\\) 次意义为「从 \\(i\\) 到 \\(j\\) 走 \\(k\\) 步可达路径数量」。 使用矩阵乘法即可解决此问题。 展开参考代码 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859#include <bits/stdc++.h>using ll = long long; // <+>using Matrix = std::array<std::array<ll, 1 << 5>, 1 << 5>;int limit, mod;void operator *= (Matrix & a, const Matrix & b) { Matrix c {}; for (int i = 0; i < limit; ++i) { for (int j = 0; j < limit; ++j) { for (int k = 0; k < limit; ++k) { c[i][j] = (c[i][j] + a[i][k] * b[k][j] % mod) % mod; } } } a = c;}int main() { std::cin.tie(nullptr) -> sync_with_stdio(false); int n, m; std::cin >> n >> m >> mod; limit = 1 << m; int s[] { 0B00000, 0B00011, 0B00110, 0B01100, 0B01111, 0B11000, 0B11011, 0B11110, }; Matrix pre {}; for (int i = 0; i < limit; ++i) { for (int j = 0; j < limit; ++j) { // ~- limit = limit - 1 if ((i | j) == ~-limit) { for (int k = 0; k < 8; ++k) { pre[i][j] |= (i & j) == s[k]; } } } } Matrix ans {}; for (int i = 0; i < limit; ++i) { ans[i][i] = 1; } while (n != 0) { if (n & 1) { ans *= pre; } pre *= pre; n >>= 1; } std::cout << ans[limit - 1][limit - 1] << "\\n"; return 0 ^ 0;} http://www.matrix67.com/blog/archives/276 已经给出解答,然而原文部分丢失。 此链接 开头便是本题的题解。↩︎","link":"vj/1194/"},{"title":"博客站遇到的若干问题与解决","text":"本文记录了派派遇到的各种问题。 公式 颜色块渲染失败 文章连接 效果 \\[ \\begin{aligned} ans &= \\sum_{k=1}^{n} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} \\colorbox{ADD8E6}{\\boxed{\\operatorname{lcm}(n-ak-bk,k)}} [\\gcd(a,b)=1] [ak+bk \\le n] \\\\ &= \\sum_{k=1}^{n} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} \\colorbox{ADD8E6}{\\boxed{\\dfrac{k(n-ak-bk)}{\\gcd(k,n)}}} \\colorbox{FFF000}{\\boxed{[\\gcd(a,b)=1]}} [ak+bk \\le n] \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} (n - ak - bk)[ak+bk \\le n] \\colorbox{FFF000}{\\boxed{\\sum_{d | \\gcd(a, b)} \\mu(d)}} \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{\\lfloor n / k \\rfloor} \\mu(d) \\sum_{a=1}^{\\lfloor n / kd \\rfloor} \\sum_{b=1}^{\\lfloor n / kd \\rfloor - a} n - akd - bkd \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{\\lfloor{n / k}\\rfloor}\\mu(d)\\left(\\dfrac{n \\lfloor n / kd\\rfloor (\\lfloor n / kd \\rfloor - 1)}{2}+\\dfrac{k d \\lfloor n / kd \\rfloor (1 - {\\lfloor n / kd \\rfloor}^{2})}{3}\\right) \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{b}\\mu(d)\\left(\\dfrac{nt(t - 1)}{2}+\\dfrac{k d t (1 - t^2)}{3}\\right) \\end{aligned} \\] 未渲染的公式 >folded12345678\\begin{aligned} ans &= \\sum_{k=1}^{n} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} \\colorbox{#ADD8E6}{\\boxed{\\operatorname{lcm}(n-ak-bk,k)}} [\\gcd(a,b)=1] [ak+bk \\le n] \\\\&= \\sum_{k=1}^{n} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} \\colorbox{#ADD8E6}{\\boxed{\\dfrac{k(n-ak-bk)}{\\gcd(k,n)}}} \\colorbox{#FFF000}{\\boxed{[\\gcd(a,b)=1]}} [ak+bk \\le n] \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{a=1}^{\\lfloor n / k \\rfloor} \\sum_{b=1}^{\\lfloor n / k \\rfloor} (n - ak - bk)[ak+bk \\le n] \\colorbox{#FFF000}{\\boxed{\\sum_{d | \\gcd(a, b)} \\mu(d)}} \\\\&= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{\\lfloor n / k \\rfloor} \\mu(d) \\sum_{a=1}^{\\lfloor n / kd \\rfloor} \\sum_{b=1}^{\\lfloor n / kd \\rfloor - a} n - akd - bkd \\\\ &= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{\\lfloor{n / k}\\rfloor}\\mu(d)\\left(\\dfrac{n \\lfloor n / kd\\rfloor (\\lfloor n / kd \\rfloor - 1)}{2}+\\dfrac{k d \\lfloor n / kd \\rfloor (1 - {\\lfloor n / kd \\rfloor}^{2})}{3}\\right) \\\\&= \\sum_{k=1}^{n} \\dfrac{k}{\\gcd(k,n)} \\sum_{d=1}^{b}\\mu(d)\\left(\\dfrac{nt(t - 1)}{2}+\\dfrac{k d t (1 - t^2)}{3}\\right)\\end{aligned} 解决 去掉 \\colorbox{#color}{anything} 中的 # 即可。","link":"etc/bugs/"},{"title":"Diary_20220918","text":"bacf46657b10ee98ba12bb7465d3265f2575c7b9e209757f3995eebf6503dd19a9039cf22a22d0b63cf3de1b3d0d7e9e0cd092b961a1afd67af09b0047bb960f3c9a451dd23546e965c0ba75f87f9bf3bc33eeb7ee512e2cea9823063ab26803c2318eb3f4dc7e536a6b5720c2c9ea90aca936b9064309a29ae6414cca67932eda3c3b37274c73f1fd1ad35324e817ef47cdb05634218c83593ec81d024fe8b11a35e2818e09bb3c99a93e1d0b86381d878cd76b543fbf820425be239bf97b9110cbc3692c369c06cf96d74ff713959c7b19388d27ae660445e9de93a91fda890aed290d17d66f737e5b7bb5f45592c95a9079187b80e86822b49902c49f349add443cb5aad09384be20cbfd3d59659605c4422f5d71a8d8f1292066929dc7075a444f9d40b1af58087098d82167cc7a69096d2f3cb0ea5304c8257d1a0527e9aa035d78ed19b2fa4cb19a177bbd7d457f514257f325a12cd92e3d65ce8655e8f874d59a7e57a8c9fb1aa4be3734c5a0bdeb7f6d76ffd92be6f715e9141c30e0f91cc56438caacd4524672d74e9e6a9d6fda9fbc7be02525873a18c31417c2de3116b3a572db057ecd4605f1040507485521cb811822a5fd8b1063fb9d0412b124d6cc6494fcecaa56bfd5cdef14b350389f7f620ccb39dcbd740196667dc19fb63c84d6cb4245ab7e48a728910d98077069068cd900e1739fbc5a4187a90e3c06005e6f5382713c0518aac68a4da076d31f57e8a1ab5361be99cf8acf5b71ae49c68b8fd799a527259ac82cf20cd2eed340c90cafc12a2157f87141fe73dd04cbe4b9f72c3b532247412ab1deb719f75db793101b428dca511fdcca39725ffffa4b649a36b05bb1cec9ad8a00a9a85cd9a691ed0c69bf0d1f49e6c9925d8a76654405889852da8d2f9d66d8f23c71de6eebd7e6235499c627583e4e341b91c77df628a274ee287e27a3b94698f0c938cc67b845c36967e179d218a4aa34da2b3acb79379915f0d734d720ac5da2fae9e66643b448e3da623b4daaa3d61608f12c82019c18c9118450cc3e3557070949b1b48f74b631e6e4ade9586bcc0bdc551f70f9386f47620f191608254f214d7531c060d108f358cf96a0a0db57cd18e02100faaef87606c63d4ceb4617fb7051b8504b4aebe5512f88bfd9e69be6138479e85e3e92bdabc1610e88b9e7b1ab0e2b3701c8185f10f8ad1c2a57889d6760307dfc46e93fb202d831c9d313a513dde24efdc4a9e83e6a856084dc7b9a7958a7215e18de0ae4e7ba1424387fae0131db7b32f8f57db736982e043de4524f132b1afbaaf63242d04eda8edfd152beb90e96d0a7bcaf1432710d3338ba9c98d1b7b3e9c0a58d4fa2bde3774b9ea053e57d5b9ab82177727bdfaac5ce2fbb158e527c558abe98f67cdb9e45101feb0ca8b768af95d38a9f3cb96a43843ecd1ffd8bde91507a2df0dd52a2443424dad7163635049d00ea9838da7e3381b541b5a4a39937ba6fddd1e1ae7eaa73da3d522ea0ecff8974b32038d04268041ab803210782d1a03e3a09cba61ce3944e7fa66425699e7d8df947c619942a5cfef6bf2ff9e5e910f2f016dbf743089aba01bcc3fa05d1f8687c51634b56e9446b347dd48d42cc18dc453ac8d7801bfbd0d16ea9200888ef3a95ba671aff06189c6212c58793cf5810f05892e0b0004bcae53be56e63cfddf662a19f187a3259b9ce504a90e971e6ca81103355d13e8e02b332699226d7e9e7eb0bbf74967f276460b66211bbc625c9ecb4258219b158672bc2bb5bd73b9b0c926a161557015828eb432459deedfa6077882ae1e66d9665de837d172c7ecfced918e725225cf95346fb0b6d096d462ca0cbc132e0c2efec4416574e2865609db1c7cce99464fcf0a6e14a465a0c28452833eaec9a90874d0ecd8d01ab13d2fc648e974d17bd3709b2895022db38d9fce65100385f6b02dbaa70e079a950cdb859c1a6fd9fc84337dd5e2011545645a2f1dd903fc3cc0d77c4805f48328b6b525578e8917f2483d6e297476ae2cd52ab41aeca21aa635446ac44ddf5045bca414e8f68071f286f39f65259142918e1d2e844eb9d7a6944eb6b3e2cfefe0ea623bde8d5aec93661b913979345d504a6094a068b38d9e9fd19dea204071fe5d935df8ed94b19124829d6566b434d04ea6386197614624777bfef8853201c3a4d0cfbb0d5470e24a3d00abb9916f12855a8970a7e66f3b09996a3ad2bed72460f36337ad92056aa4990e9347c962da5f5ebe8a5b9b07bd1a75aa672d2bddbe3b50f89c77c9bb7835c78da44a4a70c686c1aae7495d2bcaf5a0f7ef8b690c4bef8e96f3925c56bbd36de4bca160a60e99ea85e73105768eefbcac6607c9b931215a7e4e9e1eebc2a31e702290f575914ad07bca2f2f4f03351ed27a6ec9166a305068641a2ddb63546ec1819ea999b708280746c657a3bf697469bdd2ed0b044a407bee1518c16b376ce7177a993ad445dd05543ca255fa688d2b85b66d632eb279900f9784787d6f370ac9c64a977fb4a8232e9f9e490b1841bdaf18c 请输入密码查看 🍺。","link":"diary/2022/9/18/"},{"title":"二维偏序比较","text":"行表示第一维,即 \\((\\boxed{a_i}, b_i) \\prec (\\boxed{a_j}, b_j)\\)。 除第一维是 \\(\\ge\\) 外,其余情况还需满足可重。否则退化为普通情形,即直接按照符号(不带等号)排。 \\(\\le\\) \\(\\lt\\) \\(\\ge\\) \\(\\gt\\) \\(\\le\\) 默认 查询时使用 query(x-1) 离散化时逆序排序 结合 \\(\\lt\\) 和 \\(\\ge\\) \\(\\lt\\) 第二维逆序排序 结合 结合 结合 \\(\\ge\\) 第一维逆序排序 结合 结合 结合 \\(\\gt\\) 两维都逆序排序 结合 结合 结合","link":"2d-precede-compare/"},{"title":"HEXO 博客站配置","text":"优化永久链接 \\(\\verb|https://example.com/|\\rm\\sout{2022/07/05}\\verb|/anything/|\\) 📅 默认的永久链接还标注了日期,大多时候这都是不必要的。首先在 _config.yml 中设置: _config.yml12345permalink: ':permalink'permalink_defaults: nullpretty_urls: trailing_index: false trailing_html: false 这里的 :permalink 表示的是在每篇文章开头的 front-matter 部分的变量。例如本文对应的是: 123456---title: HEXO 博客站配置date: 2022-08-15 09:47:24permalink: /blogconfig/categories: etc--- 这样文章就没有冗余的日期信息了。 新增页面 \\(\\verb|https://example.com/about/|\\) 在命令行中: 1hexo new page about 随后编辑 source/about/index.md 文件添加 layout: about 。 接下来可以写各种内容,整篇文章将会显示在 https://example.com/about/ 中。 \\(\\verb|https://example.com/tags/|\\) 同上,但多数主题自带了 tags 的布局。 \\(\\verb|https://example.com/categories/|\\) 同上。但这个页面作用还是非常大的,常常希望在某一页面内放特定主题的文章,例如 https://example.com/cf/ 就可以直接到所有 cf 相关的博客内。 其实,这就是分类页面 categories 的作用。 \\(\\verb|https://example.com/|\\rm\\sout{categories}\\verb|/anything/|\\) 默认的分类页面需要用形如 https://example.com/categories/xxx/ 来访问,这样的链接并不美观。在 _config.yml 文件中设置: _config.yml1category_dir: '' 这样就可以「摆脱 categories」了! 同时,这也很好地与笔者的 permalink 习惯匹配。笔者通常会使用 cf/2/a/ 这样的链接,如果不进行上述修改,就不会有 /cf/ 这个页面,这令人感到十分不自然。 最后附上 _config.yml 与主题的配置文件 _config.icarus.yml: _config.yml >folded123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111title: Pisubtitle: patricky@patricky-taudescription: ICPCer, ArchLinux userkeywords: CS, Algorithm, Neovim, Vim, ArchLinux, Linux, Math, Number theory, Graphauthor: Patrickylanguage: zh-CNtimezone: ''url: https://tau.gayroot: /permalink: ':permalink'permalink_defaults: nullpretty_urls: trailing_index: false trailing_html: falsesource_dir: sourcepublic_dir: publictag_dir: tagsarchive_dir: archivescategory_dir: ''code_dir: downloads/codei18n_dir: ':lang'skip_render: nulldefault_layout: postnew_post_name: ':title.md'auto_spacing: truetitlecase: trueexternal_link: enable: false field: site exclude: ''filename_case: 0render_drafts: falsepost_asset_folder: falserelative_link: truefuture: falsehighlight: enable: true line_number: true auto_detect: false tab_replace: '' wrap: true hljs: falseprismjs: enable: false preprocess: true line_number: true tab_replace: ''index_generator: path: index per_page: 7 order_by: '-rating' pagination_dir: ''exclude_generator: - indexdefault_category: uncategorizedcategory_map: nulltag_map: nullmeta_generator: truedate_format: YYYY-MM-DDtime_format: HH:mm:ssupdated_option: mtimeper_page: 1000pagination_dir: ''mathjax: tags: none single_dollars: true cjk_width: 0.9 normal_width: 0.6 append_css: true every_page: true extension_options: {} # you can put your extension options here # see http://docs.mathjax.org/en/latest/options/input/tex.html#tex-extension-options for more detailpandoc: pandoc_path: /usr/bin/pandocfeed: type: atom icon: icon.jpeg path: /atom.xml limit: 0encrypt: abstract: 主人加密了此文章 🐸 message: 请输入密码查看 🍺。 wrong_pass_message: 密码错误! wrong_hash_message: HASH CODE CRUSHED! theme: shrinksitemap: path: /sitemap.xmltheme: icarusdeploy: type: git repo: patricky:patricky-tau/patricky-tau.github.io.git branch: master message: '🎁: {{ now(''YYYY-MM-DD HH:mm:ss'') }}' _config.icarus.yml >folded123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125version: 5.1.0variant: defaultlogo: /img/icon.pnghead: favicon: /img/icon.svgnavbar: menu: ☁️💡🎈 贺题笔记: /acm 📓 日记: /diary 🏷️ 标签: /tags 📚 分类: /categories 💑 友链: /friends links: Join Gitter: icon: fab fa-gitter url: https://gitter.im/patricky_tau/community GitHub: icon: fab fa-github url: https://github.com/patricky-taufooter: links: Creative Commons: icon: fab fa-creative-commons url: https://creativecommons.org/ Attribution 4.0 International: icon: fab fa-creative-commons-by url: https://creativecommons.org/licenses/by/4.0/ Download on GitHub: icon: fab fa-github url: https://github.com/patricky-tau # 备案号: xxxxxxxxxxxxxxx号: https://beian.miit.gov.cnarticle: highlight: theme: atom-one-light clipboard: true fold: unfolded readtime: true update_time: auto # licenses: # Creative Commons: # icon: fab fa-creative-commons # url: https://creativecommons.org/ # Attribution: # icon: fab fa-creative-commons-by # url: https://creativecommons.org/licenses/by/4.0/ # Noncommercial: # icon: fab fa-creative-commons-nc # url: https://creativecommons.org/licenses/by-nc/4.0/search: type: insight include_pages: truecomment: type: valine app_id: xxxxx app_key: xxxxxx placeholder: 说点什么... avatar: monsterid pageSize: 20 lang: zh-cn enable_qq: true guest_info: nick,mail,linksidebar: left: sticky: true right: sticky: truewidgets: - position: left type: profile author: Patricky author_title: '' location: Chengdu, Sichuan avatar: /img/icon.svg avatar_rounded: true gravatar: # follow_link: https://github.com/patricky-tau social_links: RSS: icon: fas fa-rss url: /atom.xml Github: icon: fab fa-github url: https://github.com/patricky-tau Twitter: icon: fab fa-twitter url: https://twitter.com/patricky_tau BiliBili: icon: fab fa-bilibili url: https://space.bilibili.com/22754284 - position: left type: toc index: false collapsed: true depth: 3 - position: right type: tags order_by: name amount: 20 show_count: true - position: left type: archives - position: right type: recent_postsplugins: katex: true mathjax: false gallery: true animejs: false back_to_top: true outdated_browser: true progressbar: trueproviders: cdn: jsdelivr fontcdn: google iconcdn: fontawesome","link":"etc/blogconfig/"},{"title":"博文风格","text":"配置 📚 通过 \\(\\verb|example.com/xxx/|\\) 进入 \\(\\verb|xxx|\\) 分类,如 /acm/。 🪧 通过 \\(\\verb|example.com/tags/xxx/|\\) 查看包含 \\(\\verb|xxx|\\) 标签的内容。 特殊的,/tags/AWESOME/ 中收藏了一些笔者认为值得一做的题。 🦜 通过 <!-- more --> 分隔。这样就形成了在主页中看到的预览效果。 按照 rating 进行降序排序。 口吻 ✍ 减少「即可」,「显然」,「我们」和「我」等用辞,尽量增添客观、书面描述。 排版 📦 若代码并非讲解内容,应当使用 \\(\\verb|<details>|\\) 标签配合 \\(\\verb|<summary>|\\) 包裹起来。 💡 引用块通常都不是引用而指出重要公式、定理。 \\(\\max\\limits_{i = 1}^N \\varphi(i) = \\varphi(p).\\) 其中 \\(p\\) 为 \\(1 \\sim N\\) 中的最大素数。 📚 不使用 \\(\\verb|****|\\) 围住加粗内容,而总使用 \\(\\verb|<b></b>|\\)。斜体、底线、删除线、行内代码亦然。 〰️ 笔者最近喜欢波浪线 即 <u style=\"text-decoration-style:wavy;\"></u> 公式排版 使用 \\(\\verb|\\verb|\\) 围住写在 \\(\\LaTeX\\) 中的代码,有的时候也用来取代 \\(\\verb|<code></code>|\\)。 如果行内公式包含上下限,总使用 \\(\\verb|\\limits|\\) 修饰。 使用 \\(\\verb|\\dfrac|\\) \\(\\frac{a / b}{ \\sum \\limits _{i = 1} ^ N i ^ 3 } \\rightarrow \\dfrac{a / b}{\\displaystyle\\sum_{i = 1} ^ N i ^ 3}\\) 合适的括号大小。总使用 \\left \\right \\(\\boxed{\\{ \\dfrac{\\dfrac{a}{c}}{b} \\}} \\rightarrow \\boxed{\\left \\{ \\dfrac{a / c}{b} \\right \\}}\\) 公式较多的情况下,使用 \\(\\color{red}\\verb|\\color|\\) 与 \\(\\boxed{\\verb|\\boxed|}\\) 标注主要内容。 \\(\\cdots\\)","link":"etc/blogstyle/"},{"title":"目前的代码风格","text":"笔者将减少奇奇怪怪的位运算和宏定义的使用。下面是较新文章中的代码部分。 大括号齐行 尽量不压行 四空格缩进 必要的注释 严格使用 namespace,其中 ::std::cin 的意义如同 /usr/bin。实际上是为了跳出多重 namespace 的屏蔽,大多时候都可以直接 std::cin。 强转都用 static_cast 尽可能使用 and / or / not 来代替 && / || / !。除非是判定一个数是否为 \\(0\\) 时的 !!x 毕竟static_cast<bool>(x)实在太长了。 any.cpp >folded12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758#include <bits/stdc++.h>void solve() { ::std::string s; ::std::cin >> s; int n = static_cast<int>(s.size()); ::std::vector lcp(n + 2, ::std::vector(n + 2, 0)); for (int i : ::std::ranges::iota_view(0, n + 1) | ::std::views::reverse) { for (int j : ::std::ranges::iota_view(i, n + 1)) { if (s[i] == s[j]) { lcp[i][j] = lcp[i + 1][j + 1] + 1; } } } ::std::vector dp(n + 2, ::std::vector(n + 2, 0)); for (int i : ::std::ranges::iota_view(0, n)) { for (int j : ::std::ranges::iota_view(i + 1, n)) { if (lcp[i][j] >= j - i) { dp[j][j - i] += 1; } } } for (int i : ::std::ranges::iota_view(0, n)) { int dimesion = 2; while (dimesion --) { for (int j : ::std::ranges::iota_view(1, n / 2 + 1)) { dp[i][j] += dp[i][j - 1]; } } } long long ans { }; for (int i : ::std::ranges::iota_view(0, n)) { for (int j : ::std::ranges::iota_view(i, n + 1)) { int x = ::std::min(lcp[i][j], j - i - 1) - 1; if (x >= 0) { ans += dp[i][x]; } } } ::std::cout << ans << "\\n";}int main() { ::std::ios::sync_with_stdio( not ::std::cin.tie(nullptr) ); int tests; ::std::cin >> tests; while (tests --) { solve(); } return 0;} 实际上笔者没有比较固定的代码风格。有的时候甚至会写出下面这样的代码(逃) 12345per(i,~-n,0)s+=1,ans+=kuhn(i);if(ans!=n)return puts("No Answer"),0x0;rep(i,n,~-n<<1|1)L[f[i]]=i-n;rep(i,0,~-n)printf("%d%c",L[i]," \\n"[i==n]);","link":"etc/codestyle/"},{"title":"浮点数的比较符","text":"计算几何符号比较表 意义 写法 \\(a = b\\) fabs(a - b) < epsilon \\(a \\ne b\\) fabs(a - b) > epsilon \\(a < b\\) a - b < - epsilon \\(a \\le b\\) a - b < epsilon \\(a > b\\) a - b > epsilon \\(a \\ge b\\) a - b > - epsilon","link":"double-compare/"},{"title":"若干杂题、水题","text":"本文记录了若干题目,难以分类的同时很可能也没有 OJ 测。 有的是记不清来源的题,甚至仅仅是听过一次,有的也可能只是口胡的题,还有的只是因为题目太水但还是想放在网站上所以挂在这里懒得另开一篇新文。 .post-summary { display: none; } 子集和 每一个元素在 \\(2 ^ n\\) 个子集出现 \\(2 ^ {n - 1}\\) 次,因此答案为 \\(2 ^ {n - 1} \\times \\displaystyle\\sum\\limits_{i = 1}^n a_i.\\) 子段和 考虑每一个元素前驱后继覆盖到的区间数量,两部分独立,因此答案为 \\(\\displaystyle\\sum\\limits_{i = 1}^n i \\times (n - i) \\times a_i\\) 子段最值和 https://codeforces.com/contest/817/problem/D 从目标来看,任何子段的最值至多有 \\(n\\) 种取值,因此考虑每个 \\(a_i\\) 对哪些区间产生了贡献:答案明显是两边的 \\(\\rm NGE\\),可使用单调栈。 123456789101112131415161718long long ans { };auto calc = [&](auto cmp, int sign) { ::std::vector stk(n + 1, 0), l(stk), r(stk); int top { }; for (int i : ::std::ranges::iota_view(1, n + 1)) { while (top and cmp(a[i], a[stk[top]])) { r[stk[top --]] = i - 1; } l[i] = stk[top] + 1; stk[++top] = i; } while (top) { r[stk[top --]] = n; } for (int i : ::std::ranges::iota_view(1, n + 1)) { ans += sign * static_cast<long long>(i - l[i] + 1) * (r[i] - i + 1) * a[i]; }}; 子段 gcd 和 每个 \\(\\rm gcd\\) 的贡献分为左右两部分实际上不如直接向右,因为有 \\(\\gcd(x, y) \\le \\min(x, y)\\) 这样的单调性,只需记下每一个 \\(\\rm gcd\\) 的出现次数。 https://www.spoj.com/problems/ADAGF/ spoj-ADAGF.cpp >folded12345678910111213141516171819202122232425#include <bits/stdc++.h>int main() { ::std::ios::sync_with_stdio(not ::std::cin.tie(nullptr)); int n; ::std::cin >> n; ::std::vector<int> a(n + 1); for (int i = 1; i <= n; ++i) { ::std::cin >> a[i]; } long long ans = 0; ::std::map<int, int> mp, nmp; for (int i = 1; i <= n; i++) { nmp.clear(); nmp[a[i]] = 1; for (auto [k, v] : mp) { nmp[::std::__gcd(a[i], k)] += v; } for (auto [k, v] : nmp) { ans += static_cast<long long>(k) * v; } mp = ::std::move(nmp); // mp = nmp } ::std::cout << ans << "\\n"; return 0;} 最少修改多少数使得原数组非严格递增 考虑贪心,\\(n - \\mathop{\\rm LIS}\\limits_{i=1}^n\\{a_i\\}\\) 即为所求。其中 \\(\\rm LIS\\) 非严格。 最少修改多少数使得原数组严格递增 如果两数大小关系不小于间隔距离,那么这两个点可以保留。形式化地说,需要满足: \\[ a_j - a_i \\ge j - i \\Rightarrow a_j - j \\ge a_i - i \\] 于是 \\(n - \\mathop{\\rm LIS}\\limits_{i=1}^n\\{a_i - i\\}\\) 即为所求。 最大子段积 需要考虑负数,因此维护两个最值。 max-subarray-product.cpp1234567long long m, M, ans;m = M = ans = a[0];for (int i : a | ::std::views::drop(1)) { ::std::tie(m, M) = ::std::minmax({ i, i * m, i * M }); ans = ::std::max(ans, M);}","link":"acm/etc/"}],"tags":[{"name":"AWESOME","slug":"AWESOME","link":"tags/AWESOME/"},{"name":"树状数组","slug":"树状数组","link":"tags/%E6%A0%91%E7%8A%B6%E6%95%B0%E7%BB%84/"},{"name":"LIS","slug":"LIS","link":"tags/LIS/"},{"name":"dp","slug":"dp","link":"tags/dp/"},{"name":"背包","slug":"背包","link":"tags/%E8%83%8C%E5%8C%85/"},{"name":"组合","slug":"组合","link":"tags/%E7%BB%84%E5%90%88/"},{"name":"DSU on tree","slug":"DSU-on-tree","link":"tags/DSU-on-tree/"},{"name":"DFS","slug":"DFS","link":"tags/DFS/"},{"name":"二维偏序","slug":"二维偏序","link":"tags/%E4%BA%8C%E7%BB%B4%E5%81%8F%E5%BA%8F/"},{"name":"nge","slug":"nge","link":"tags/nge/"},{"name":"线段树","slug":"线段树","link":"tags/%E7%BA%BF%E6%AE%B5%E6%A0%91/"},{"name":"STL","slug":"STL","link":"tags/STL/"},{"name":"贪心","slug":"贪心","link":"tags/%E8%B4%AA%E5%BF%83/"},{"name":"构造","slug":"构造","link":"tags/%E6%9E%84%E9%80%A0/"},{"name":"二分","slug":"二分","link":"tags/%E4%BA%8C%E5%88%86/"},{"name":"数论","slug":"数论","link":"tags/%E6%95%B0%E8%AE%BA/"},{"name":"算术基本定理","slug":"算术基本定理","link":"tags/%E7%AE%97%E6%9C%AF%E5%9F%BA%E6%9C%AC%E5%AE%9A%E7%90%86/"},{"name":"拓扑排序","slug":"拓扑排序","link":"tags/%E6%8B%93%E6%89%91%E6%8E%92%E5%BA%8F/"},{"name":"组合数学","slug":"组合数学","link":"tags/%E7%BB%84%E5%90%88%E6%95%B0%E5%AD%A6/"},{"name":"欧拉函数","slug":"欧拉函数","link":"tags/%E6%AC%A7%E6%8B%89%E5%87%BD%E6%95%B0/"},{"name":"莫比乌斯反演","slug":"莫比乌斯反演","link":"tags/%E8%8E%AB%E6%AF%94%E4%B9%8C%E6%96%AF%E5%8F%8D%E6%BC%94/"},{"name":"博弈","slug":"博弈","link":"tags/%E5%8D%9A%E5%BC%88/"},{"name":"异或","slug":"异或","link":"tags/%E5%BC%82%E6%88%96/"},{"name":"前缀和","slug":"前缀和","link":"tags/%E5%89%8D%E7%BC%80%E5%92%8C/"},{"name":"正则表达式","slug":"正则表达式","link":"tags/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F/"},{"name":"TODO","slug":"TODO","link":"tags/TODO/"},{"name":"计算几何","slug":"计算几何","link":"tags/%E8%AE%A1%E7%AE%97%E5%87%A0%E4%BD%95/"},{"name":"浮点gcd","slug":"浮点gcd","link":"tags/%E6%B5%AE%E7%82%B9gcd/"},{"name":"记录路径","slug":"记录路径","link":"tags/%E8%AE%B0%E5%BD%95%E8%B7%AF%E5%BE%84/"},{"name":"阿圆","slug":"阿圆","link":"tags/%E9%98%BF%E5%9C%86/"},{"name":"切比雪夫距离","slug":"切比雪夫距离","link":"tags/%E5%88%87%E6%AF%94%E9%9B%AA%E5%A4%AB%E8%B7%9D%E7%A6%BB/"},{"name":"模拟","slug":"模拟","link":"tags/%E6%A8%A1%E6%8B%9F/"},{"name":"括号序列","slug":"括号序列","link":"tags/%E6%8B%AC%E5%8F%B7%E5%BA%8F%E5%88%97/"},{"name":"BFS","slug":"BFS","link":"tags/BFS/"},{"name":"graph","slug":"graph","link":"tags/graph/"},{"name":"trie","slug":"trie","link":"tags/trie/"},{"name":"floyd算法","slug":"floyd算法","link":"tags/floyd%E7%AE%97%E6%B3%95/"},{"name":"矩阵乘法","slug":"矩阵乘法","link":"tags/%E7%9F%A9%E9%98%B5%E4%B9%98%E6%B3%95/"}],"categories":[{"name":"51nod","slug":"51nod","link":"51nod/"},{"name":"acm","slug":"acm","link":"acm/"},{"name":"cf","slug":"cf","link":"cf/"},{"name":"cc","slug":"cc","link":"cc/"},{"name":"1311","slug":"cf/1311","link":"cf/1311/"},{"name":"1712","slug":"cf/1712","link":"cf/1712/"},{"name":"1313","slug":"cf/1313","link":"cf/1313/"},{"name":"2020","slug":"cc/2020","link":"cc/2020/"},{"name":"1717","slug":"cf/1717","link":"cf/1717/"},{"name":"1320","slug":"cf/1320","link":"cf/1320/"},{"name":"1719","slug":"cf/1719","link":"cf/1719/"},{"name":"1407","slug":"cf/1407","link":"cf/1407/"},{"name":"1579","slug":"cf/1579","link":"cf/1579/"},{"name":"1","slug":"cf/1","link":"cf/1/"},{"name":"2","slug":"cf/2","link":"cf/2/"},{"name":"3","slug":"cf/3","link":"cf/3/"},{"name":"4","slug":"cf/4","link":"cf/4/"},{"name":"5","slug":"cf/5","link":"cf/5/"},{"name":"changchun","slug":"cc/2020/changchun","link":"cc/2020/changchun/"},{"name":"ec","slug":"ec","link":"ec/"},{"name":"652","slug":"cf/652","link":"cf/652/"},{"name":"nc","slug":"nc","link":"nc/"},{"name":"p","slug":"p","link":"p/"},{"name":"vj","slug":"vj","link":"vj/"},{"name":"etc","slug":"etc","link":"etc/"},{"name":"diary","slug":"diary","link":"diary/"},{"name":"2017","slug":"ec/2017","link":"ec/2017/"},{"name":"2021","slug":"ec/2021","link":"ec/2021/"},{"name":"11255","slug":"nc/11255","link":"nc/11255/"},{"name":"luogu","slug":"luogu","link":"luogu/"},{"name":"vijos","slug":"vijos","link":"vijos/"},{"name":"2022","slug":"diary/2022","link":"diary/2022/"},{"name":"xian","slug":"ec/2017/xian","link":"ec/2017/xian/"},{"name":"xian","slug":"ec/2021/xian","link":"ec/2021/xian/"},{"name":"9","slug":"diary/2022/9","link":"diary/2022/9/"},{"name":"ecfinals","slug":"ecfinals","link":"ecfinals/"},{"name":"2021","slug":"ecfinals/2021","link":"ecfinals/2021/"}],"pages":[{"title":"是派派!","text":"☁️💡🎈 正在学习 ICPC 竞赛算法,专攻数学。但啥奖也没得到。 🤔 非常向往一份前端项目的维护工作。即使对前端一窍不通。 💻 是一个 ArchLinux 用户,喜欢探索新玩具。 最近在尝试用 Lua 重写自己的 Neovim 配置。 💡 经常有一些奇怪的点子与烦恼。比如怎么都吃不胖... 同性交友网站 推特 哔站 知乎 博客介绍 博客框架为 https://hexo.io。主题: https://github.com/ppoffice/hexo-theme-icarus,略有改动。详细的配置文件见 此处 页面导航 链接 题解、做题记录 /acm/ 杂题、水题记录 /acm/etc/ 博客配置、杂项 /etc/ 友链 /friends/ 待做 /tags/TODO/","link":"index.html"},{"title":"Categories","text":"","link":"categories/index.html"},{"title":"友情链接","text":"加载中,稍等几秒... 申请友链须知 申请请提供:站点名称、站点链接、站点描述、logo或头像(免设防盗链)。 排名不分先后。刷新后重排,更新信息后请留言告知。 定期清理不符合要求的友链。 不存储友链图片,若未提供图片或图片失效使用默认图。 本站友链信息如下,申请友链前请先添加本站信息: 1234网站图标:https://tau.gay/img/icon.svg网站名称:Pi网站地址:https://tau.gay网站简介:算法竞赛,日常记录,技术分享","link":"friends/index.html"},{"title":"","text":"[{\"date\":\"2022-08-22 01:10:20\",\"src\":null,\"name\":\"临渊\",\"desc\":\"...\",\"url\":\"https://www.cnblogs.com/Haven-/\"},{\"date\":\"2022-06-22 01:10:20\",\"src\":null,\"name\":\"Bruce12138\",\"desc\":\"...\",\"url\":\"https://bruce12138.com/\"},{\"date\":\"2022-06-22 01:10:20\",\"src\":null,\"name\":\"Ximane's Story\",\"desc\":\"...\",\"url\":\"https://blog.ximena.top/\"},{\"date\":\"2022-06-22 01:10:20\",\"src\":null,\"name\":\"dianhsu\",\"desc\":\"...\",\"url\":\"https://dianhsu.com/\"},{\"date\":\"2022-08-22 01:10:20\",\"src\":\"https://z3.ax1x.com/2021/11/25/oA8JpV.jpg\",\"name\":\"zclll's Blog\",\"desc\":\"Mixologist/不调参的MLer/伦理学与政治哲学/辩手/C++/ICPCer/足球/围棋\",\"url\":\"https://zclll.com/\"}]","link":"json_data/friend.json"},{"title":"","text":"","link":"tags/index.html"}]}