2023QDEZ男人八题线上同步赛 赛时代码和思路
2023QDEZ男人八题线上同步赛 赛时代码和思路
\(\texttt{A-std}\);\(\texttt{B-std}\);\(\texttt{C-std}\);\(\texttt{D-std}\);\(\texttt{E-std}\);\(\texttt{F-std}\);\(\texttt{G-std}\);\(\texttt{H-std}\);\(\texttt{Ex-std}\)
我:\(50+50+20+5+30+45+0+0+0=200\text{pts}\).
喜提二中比赛除了二中选手以外的 rk1(大雾
以下是赛时代码和思路,省略快读和头文件等。
PS:因为博客太长了不好看,所以可能把源代码里的空行删去了。
前置代码说明:
#include <bits/stdc++.h> using namespace std; #define rep(i, x) for (int i = 0; i < (x); ++i) #define rr read() inline int read() { // 有的时候换成了 long long int num = 0, flag = 1, ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') flag = -1; for (; isdigit(ch); ch = getchar()) num = num * 10 + ch - '0'; return num * flag; }
T1
一个数减 \(k\) 次一,每次操作对答案的贡献为,最后面一个 \(1\) 是倒数第几个数。
先打暴力!
void solve(ll n, ll k) { ll res = 0; while (k--) { auto t = n; --n, ++res; ll kk = 1; while ((t & 1) == 0) ++res, t >>= 1, ++kk; cerr << n + 1 << " - " << n << ": " << kk << endl; } printf("%lld\n", res); } signed main() { int T = rr; while (T--) { ll n = rr, k = rr; solve(n, k); } return 0; }
然后想想,\(10^{18}\),在我的知识范围内,除了数学大概也只有数学加持的动规了。
咋数学呢?先查分吧,好想。
就是 \(f(r)-f(l-1)\) 然后 \(f(x)\) 怎么算。
然后发现从后面一位一位的看,倒数第 \(i\) 位的 \(1\) 似乎需要 \(2^i\) 次 \(-1\) 操作才能消掉。
发现规律,解决!
ll solve(ll x) { ll res = 0; while (x) res += x, x >>= 1; return res; } signed main() { int T = rr; while (T--) { ll n = rr, k = rr; if (n == k) printf("%lld\n", solve(n)); else printf("%lld\n", solve(n) - solve(n - k)); } return 0; }
T2
求本质不同的子序列个数。
题目背景说了是动规,干嘛不想想是不是动规。
想不到,先打暴力。
using ll = long long; using vi = vector<int>; const ll MOD = 998244353; int n; vi a; set<pair<int, vi>> mem; ll cnt = 0; void dfs(int k, vi now) { if (mem.count({k, now})) return; mem.insert({k, now}); if (k == n) { cnt = (cnt + 1) % MOD; return; } dfs(k + 1, now); now.push_back(a[k]); dfs(k + 1, now); } signed main() { n = rr; rep(i, n) a.push_back(rr); dfs(0, vi()); printf("%lld\n", cnt); return 0; }
然后发现每一个数,要么加要么不加,优化(似乎并不快多少
using ll = long long; using vi = vector<int>; const ll MOD = 998244353; int n; set<vi> res; signed main() { n = rr; res.emplace(vi()); rep(i, n) { int x = rr; auto bak = res; for (auto t : bak) t.push_back(x), res.emplace(t); } printf("%lld\n", ll(res.size()) % MOD); return 0; }
然后不会优化了,老老实实找规律吧。
不久发现了规律。
设 \(f(i)\) 表示以 \(i\) 结尾的本质不同的子序列的个数。
先不考虑「本质不同」的要求,从左到右考虑:\(f(i)=\sum\limits_{j=1}^if(j)+\text{第 }i\text{ 个是否是该数字第一次出现}\).
然后考虑这个要求呢?发现如果这个数字不是第一次出现,那么它就不能从「前面与它相同的数字」的前面转移,因为那些东西已经被前面的那个数字计算过了。
特殊的,\(f(1)=1\)。然后再前缀和优化一下。
答案即是 \(\sum\limits_{i=1}^nf(i)\)。于是,解决。
using ll = long long; using vi = vector<int>; const int N = 1e6 + 10; const ll MOD = 998244353; map<int, int> appear; int isfirst[N], tolast[N]; ll f[N], s[N]; signed main() { int n = rr; for (int i = 1; i <= n; ++i) { int a = rr; appear.count(a) ? tolast[i] = appear[a] : tolast[i] = isfirst[i] = 1; appear[a] = i; } f[1] = s[1] = 1; for (int i = 2; i <= n; ++i) { f[i] = (s[i - 1] - s[tolast[i] - 1] + isfirst[i] + MOD) % MOD; s[i] = (s[i - 1] + f[i]) % MOD; } printf("%lld\n", (s[n] + 1) % MOD); return 0; }
T3
给定一个序列,多组询问,每组询问查询 \([l,r]\) 内极差为 \(k\) 的子区间数量。
思考过程:没有,不会,ST 表(打暴力)拿个部分分就完了。
const int N = 1e6 + 10; const int K = 22; int lg[N]; int f1[N][K], f2[N][K]; int q1(int l, int r) { int len = r - l + 1, k = lg[len]; return max(f1[l][k], f1[r - (1 << k) + 1][k]); } int q2(int l, int r) { int len = r - l + 1, k = lg[len]; return min(f2[l][k], f2[r - (1 << k) + 1][k]); } int main() { lg[1] = 0; for (int i = 2; i < N; ++i) lg[i] = lg[i >> 1] + 1; int n = rr, m = rr, k = rr; for (int i = 1; i <= n; ++i) f1[i][0] = f2[i][0] = rr; for (int j = 1; j < K; ++j) for (int i = 1; i + (1 << j) - 1 <= n; ++i) { f1[i][j] = max(f1[i][j - 1], f1[i + (1 << j - 1)][j - 1]); f2[i][j] = min(f2[i][j - 1], f2[i + (1 << j - 1)][j - 1]); } while (m--) { int l = rr, r = rr; ll res = 0; for (int i = l; i <= r; ++i) for (int j = i; j <= r; ++j) if (q1(i, j) - q2(i, j) == k) ++res; printf("%lld\n", res); } return 0; }
T4
有 \(2n\) 个二元组 \((a_i,b_i)\),现要两两分组,每一组对答案的贡献为,\(a+b\) 较大(如果相等则是 \(b\) 较大)的那个二元组的 \(a\).
思路:DFS 暴力偏分呗。
但是我懒,写了个全排列 next_permutation
就走人了,\(10\to5\text{pts}\)。
using ll = long long; using pii = pair<int, int>; using vi = vector<int>; using vp = vector<pii>; #define sum(x) (x.first + x.second) int calc(pii a, pii b) { if (sum(a) != sum(b)) return sum(a) > sum(b) ? a.first : b.first; else return a.second > b.second ? a.first : b.first; } signed main() { int n = rr, _1, _2; vp a; vi p; rep(_, 2 * n) _1 = rr, _2 = rr, a.push_back({_1, _2}), p.push_back(_); ll ans = 4e18; do { ll res = 0; for (int l = 0; l + 1 < p.size(); l += 2) res += calc(a[p[l]], a[p[l + 1]]); if (res < ans) ans = res; } while (next_permutation(p.begin(), p.end())); printf("%lld\n", ans); return 0; }
T5
有一个排列,还有很多很多的操作,操作种类分为「« 循环右移一位 »、« 翻转 »、« 变为逆排列 »」。
一个排列 \(p\) 的逆排列定义为一个排列 \(p^{-1}\),满足 \(\forall i\in\mathbb{N}\cap[1,n],\ p_{p_i^{-1}}=i\)。
逆排列?没看懂。先模拟一下:\(\{4,3,1,2\}\to\{3,4,2,1\}\).
懂啦?没懂。再写个大的。然后才勉强理解(逃
然后,暴力(辣鸡的我。
using vi = vector<int>; void turn(vi &a) { reverse(a.begin(), a.end()); } void move(vi &a) { int t = a.back(); for (int i = a.size() - 1; i; --i) a[i] = a[i - 1]; a[0] = t; } void iv(vi &a) { int n = a.size(); vi t(n); for (int i = 0; i < n; ++i) t[a[i] - 1] = i + 1; a = t; } signed main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n, q; cin >> n >> q; vi a(n); rep(_, n) cin >> a[_]; while (q--) { string s; int c; cin >> s >> c; while (c--) for (char op : s) if (op == 'S') move(a); else if (op == 'F') turn(a); else iv(a); } for (int _ : a) printf("%d ", _); return 0; }
T6
一个有根树,以 \(1\) 为根,树上有点集 \(S\),每次点集中的每个元素向上移动,直到根 \(1\) 属于这个点集。求每次移动以后的点集大小 \(|S|\)。
不会,模拟完就走人。
const int N = 2e6 + 10; vector<int> g[N]; void add(int u, int v) { g[u].push_back(v), g[v].push_back(u); } int f[N]; void dfs(int u, int fa) { f[u] = fa; for (int v : g[u]) if (v != fa) dfs(v, u); } signed main() { int n = rr; for (int i = 1; i < n; ++i) add(rr, rr); dfs(1, -1); int s = rr; set<int> d[2]; for (int i = 1; i <= s; ++i) d[0].emplace(rr); for (int k = 1; !d[k ^ 1].count(1); k ^= 1) { d[k].clear(); for (int i : d[k ^ 1]) d[k].emplace(f[i]); printf("%d ", (int)d[k].size()); } return 0; }
然后发现可以每次就把一个节点加到它的父亲上,但似乎一点用也没有。
const int N = 2e6 + 10; vector<int> g[N]; void add(int u, int v) { g[u].push_back(v), g[v].push_back(u); } int f[N]; void dfs(int u, int fa) { f[u] = fa; for (int v : g[u]) if (v != fa) dfs(v, u); } signed main() { int n = rr; for (int i = 1; i < n; ++i) add(rr, rr); dfs(1, -1); int s = rr; set<int> d[2]; for (int i = 1; i <= s; ++i) d[0].emplace(f[rr]); for (int k = 1; !d[k ^ 1].count(-1); k ^= 1) { printf("%d ", (int)d[k ^ 1].size()); d[k].clear(); for (auto i : d[k ^ 1]) d[k].emplace(f[i]); } return 0; }
T7
数论?不知道。看不懂而已(逃
T8
很懵,也没时间了,不写了。
「咱俩到 « 天台 » 逛逛吧」(大雾
T9
我为什么要看一个只有 \(1\text{pts}\) 的附加题?
总结
« 骗分过样例 »« 暴力出奇迹 »
« 暴搜挂着机 »« 打表出省一 »
本文来自博客园,作者:RainPPR,转载请注明原文链接:https://www.cnblogs.com/RainPPR/p/2023qdez-nr8t-code.html
如有侵权请联系我(或 2125773894@qq.com)删除。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!