河南萌新联赛2024第(四)场:河南理工大学
河南萌新联赛2024第(四)场:河南理工大学
A-该出奇兵了_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
一次奇袭相当于割掉了一个点,而每割掉一个点可能会产生一个或多个连通分量,所以我们需要计算删掉一个节点后其新产生的联通分量的贡献。
以割点为分界点,分为两棵子树,在缩点过程中将删掉割点后产生的 \(son_x-1(son\text{表示x的儿子个数})\) 个子树的贡献先记录到 \(ans_u\) 里,然后 \(sum_u\) 记该子树的大小,最后再加上 \(a_u\) 表示从该割点起表示的一棵子树的大小,到时候计算剩下一棵子树直接用该联通块的大小减去 \(sum_i\) 即可。
记 \(rt_i\) 表示点 \(i\) 所在的联通块编号,\(S\) 表示某一联通块的贡献,\(N\) 表示联通块的个数,将 \(i\) 点割掉后得到了 \(m\) 个新联通分量,则对于删掉 \(i\) 点的答案为:
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; using i128 = __int128; istream &operator>>(istream &is, i128 &x) { string s; is >> s; bool neg = false; x = 0; for (char c : s) { if (c == '-') neg = true; else x = x * 10 + (c - '0'); } if (neg) x = -x; return is; } ostream &operator<<(ostream &os, i128 x) { if (x == 0) os << 0; else { string s, t; if (x < 0) x = -x, t = "-"; while (x) s.push_back('0' + x % 10), x /= 10; reverse(s.begin(), s.end()); os << t << s; } return os; } constexpr int N = 3e5 + 10; vector<i128> ans(N), sz(N), a(N), sum(N); struct SCC { int top = 0, cntscc = 0, dfncnt = 0, n; vector<int> dfn, low, stk, instk; vector<int> sccnum, sccid; vector<vector<int>> g, scc; SCC(int n_): n(n_) { //缩点 dfn.assign(n + 1, 0); low.assign(n + 1, 0); stk.assign(n + 1, 0); sccnum.assign(n + 1, 0); sccid.assign(n + 1, 0); instk.assign(n + 1, 0); g.resize(n + 1); scc.resize(n + 1); } void add(int u, int v) { g[u].push_back(v); } //缩点 void tarjan(int u) { dfn[u] = low[u] = ++dfncnt; stk[++top] = u; instk[u] = 1; i128 s = 0; for (auto v : g[u]) { if (!dfn[v]) { tarjan(v); low[u] = min(low[u], low[v]); sz[u] += sz[v]; if (dfn[u] <= low[v]) { ans[u] += sz[v] * sz[v]; s += sz[v]; } } else if (instk[v]) { low[u] = min(low[u], dfn[v]); } } sz[u] += a[u]; sum[u] = s + a[u]; if (dfn[u] == low[u]) { cntscc ++; int v; do { v = stk[top --], instk[v] = 0; sccid[v] = cntscc; scc[cntscc].push_back(v); sccnum[cntscc] ++; } while (u != v); } } void work() { for (int i = 1; i <= n; i ++) { if (!dfn[i]) { dfncnt = 0; tarjan(i); } } } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; SCC scc(n); for (int i = 1; i <= m; i ++) { int u, v; cin >> u >> v; scc.add(u, v); scc.add(v, u); } for (int i = 1; i <= n; i ++) { cin >> a[i]; } scc.work(); int cnt = scc.cntscc; auto g = scc.scc; auto rt = scc.sccid; i128 res = 0; vector<i128> s(cnt + 1); for (int i = 1; i <= cnt; i ++) { i128 x = 0; for (auto v : g[i]) { x += a[v]; } res += x * x; s[i] = x; } i128 Ans = res; for (int i = 1; i <= n; i ++) { ans[i] += res; ans[i] -= s[rt[i]] * s[rt[i]]; ans[i] += (s[rt[i]] - sum[i]) * (s[rt[i]] - sum[i]); Ans = min(Ans, ans[i]); } cout << Ans << '\n'; return 0; }
B-小雷的神奇电脑_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
字典树最大异或对的板子,但是这题是求最大同或对,也就是只需要把查找部分稍微改一下即可。
官方题解给的是排序之后取相邻两数最小的异或值,取最大同或对也就是取 \(2^m-1\) 减去最小异或对即可,赛时想过,但不会证明。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; const int N = 2e5 + 5, M = 3e6 + 5; int n,m, a[N], son[M][2], cnt; void insert(int x) { int p = 0; for (int i = m - 1; ~i; i--) { int &s = son[p][x >> i & 1]; if (!s) s = ++cnt; p = s; } } int query(int x) { int ans = 0, p = 0; for (int i = m - 1; ~i; i--) { int s = x >> i & 1; if (son[p][s]) ans += 1 << i, p = son[p][s]; else p = son[p][!s]; } return ans; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m; int ans = 0; for (int i = 0; i < n; i++) { cin >> a[i]; ans = max(ans, query(a[i])); insert(a[i]); } cout << ans << '\n'; return 0; }
C-岗位分配_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
组合数学。
考虑将 \(\sum\limits_{i=1}^na_i\) 的所有人分配完后,其实就将剩下的 \(m-\sum a_i\) 人以及再加上 \(n\) 个‘空’人分配给 \(n\) 个位置。
代码
int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; for (int i = 1; i <= n; i ++) { int x; cin >> x; m -= x; } cout << comb.C(m + n, n) << '\n'; return 0; }
D-简单的素数_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
素数判断。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; void solve() { i64 x; cin >> x; if(x==2){ cout << "Yes\n"; return ; } for (int i = 2; i <= sqrt(x); i ++) { if (x % i == 0) { cout << "No\n"; return ; } } cout << "Yes\n"; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
E-AND_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
用 Meissel_Lehmer 筛出 \(1\sim x\) 和 \(1\sim y\) 的素数个数,然后相减即可,考虑如果 \(x>2\),那么其余素数无论怎么做与运算都会在二进制 \(0\) 位剩个 \(1\),所以只有 \([2,其他素数]\) 的区间才是 \(AND\) 和为 \(0\),当然 \([2,2],[2,3]\) 是不行的,要减去这两个。
另外朴素的欧拉筛也可以通过本题。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; #define div(a, b) (1.0 * (a) / (b)) #define half(x) (((x) - 1) / 2) i64 Meissel_Lehmer(i64 n) { if (n <= 3) { return max(n - 1, 0LL); } long long v = sqrtl(n); int s = (v + 1) / 2; vector<int> smalls(s), roughs(s); vector<i64> larges(s); for (int i = 0 ; i < s ; i++) { smalls[i] = i; } for (int i = 0 ; i < s ; i++) { roughs[i] = i * 2 + 1; } for (int i = 0 ; i < s ; i++) { larges[i] = half(n / roughs[i]); } vector<bool> skip(v + 1); int pc = 0; for (int p = 3 ; p <= v ; p += 2) { if (skip[p] == false) { i64 q = p * p; if (q * q > n) { break; } skip[p] = true; for (int i = q ; i <= v ; i += 2 * p) { skip[i] = true; } int ns = 0; for (int k = 0 ; k < s ; k++) { int i = roughs[k]; if (skip[i]) { continue; } long long d = 1LL * i * p; larges[ns] = larges[k] - (d <= v ? larges[smalls[d >> 1] - pc] : smalls[half(div(n, d))]) + pc; roughs[ns++] = i; } s = ns; for (int i = half(v), j = (((v / p) - 1) | 1) ; j >= p ; j -= 2) { int c = smalls[j / 2] - pc; for (int e = j * p / 2 ; i >= e ; i--) { smalls[i] -= c; } } pc++; } } larges[0] += 1LL * (s + 2 * (pc - 1)) * (s - 1) >> 1; for (int k = 1 ; k < s ; k++) { larges[0] -= larges[k]; } for (int L = 1 ; L < s ; L++) { int q = roughs[L]; long long m = n / q; int e = smalls[half(m / q)] - pc; if (e < L + 1) { break; } long long t = 0; for (int k = L + 1 ; k <= e ; k++) { t += smalls[half(div(m, roughs[k]))]; } larges[0] += t - 1LL * (e - L) * (pc + L - 1); } return larges[0] + 1; } #undef div #undef half void solve() { int x, y; cin >> x >> y; int ans = Meissel_Lehmer(y) - Meissel_Lehmer(x - 1); cout << ans << ' '; cout << (x > 2 ? 0 : max(0, ans - 2)) << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
F-小雷的算式_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
字符串模拟。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); string s; cin >> s; vector<i64> num; for (int i = 0; i < s.size(); i ++) { if (s[i] != '+') { i64 j = i, x = s[i] - '0'; while (j + 1 < s.size() && s[j + 1] != '+') { j ++; x = x * 10 + s[j] - '0'; } num.emplace_back(x); i = j; } } sort(num.begin(), num.end(), greater<>()); i64 ans = 0; for (int i = 0; i < num.size(); i ++) { ans += num[i]; cout << num[i]; if (i != num.size() - 1)cout << '+'; } cout << '\n' << ans << '\n'; return 0; }
G-循环字符串_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
线段树维护正反哈希值,当且存在长度为 \(x\) 的循环节时满足的充要条件为 \(hash(l+x,r)=hash(l,r-x)\),需要预先处理质因数去枚举最小循环节长度,每次还需要除去循环的次数。
官方题解说预处理约数的做法是会被卡的,但赛时 \(1\) 血就是枚举约数
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; using u64 = unsigned long long; constexpr int p = 13331, M = 2e6 + 10; u64 P[M], hs[M], f[M]; template<class Node> struct SegmentTree { #define lc u<<1 #define rc u<<1|1 const int n, N; vector<Node> tr; SegmentTree(): n(0) {} SegmentTree(int n_): n(n_), N(n * 4 + 10) { tr.reserve(N); tr.resize(N); } SegmentTree(vector<int> init) : SegmentTree(init.size()) { function<void(int, int, int)> build = [&](int u, int l, int r) { tr[u].l = l, tr[u].r = r; init_lazy(tr[u]); if (l == r) { tr[u] = {l, r, (u64)init[l], (u64)init[l], (u64)0}; return ; } i64 mid = (l + r) >> 1; build(lc, l, mid); build(rc, mid + 1, r); pushup(tr[u], tr[lc], tr[rc]); }; build(1, 1, n); } void cal_lazy(Node & fa, Node & ch) { ch.h[0] = ch.h[1] = fa.lazy * f[(ch.r - ch.l)]; } void tag_union(Node& fa, Node& ch) { ch.lazy = fa.lazy; } void init_lazy(Node& u) { u.lazy = 0; } void pushdown(i64 u) { if (tr[u].lazy != 0) { cal_lazy(tr[u], tr[lc]); cal_lazy(tr[u], tr[rc]); tag_union(tr[u], tr[lc]); tag_union(tr[u], tr[rc]); init_lazy(tr[u]); } } void pushup(Node& U, Node& L, Node& R) { //上传 U.l = L.l; U.r = R.r; U.h[0] = L.h[0] * P[R.r - R.l + 1] + R.h[0]; U.h[1] = R.h[1] * P[L.r - L.l + 1] + L.h[1]; } void modify(int u, int l, int r, int k) { if (tr[u].l >= l && tr[u].r <= r) { tr[u].lazy = k; tr[u].h[0] = tr[u].h[1] = k * f[(tr[u].r - tr[u].l)]; return ; } pushdown(u); int mid = (tr[u].l + tr[u].r) >> 1; if (l <= mid) modify(lc, l, r, k); if (r > mid) modify(rc, l, r, k); pushup(tr[u], tr[lc], tr[rc]); } Node query(int u, int l, int r) { //区查 if (l <= tr[u].l && tr[u].r <= r) return tr[u]; int mid = tr[u].l + tr[u].r >> 1; pushdown(u); if (r <= mid) return query(lc, l, r); if (l > mid) return query(rc, l, r); Node U; Node L = query(lc, l, r), R = query(rc, l, r); pushup(U, L, R); return U; } }; struct Node { //线段树定义 i64 l, r; u64 h[2]; u64 lazy; }; vector<int> euler_range(int n) { vector<int> phi(n + 1), prime; vector<bool> is_prime(n + 1, true); is_prime[1] = 0, phi[1] = 1; for (int i = 2; i <= n; i++) { if (is_prime[i]) prime.push_back(i), phi[i] = i; for (int j = 0; j < (int)prime.size() && i * prime[j] <= n; j++) { is_prime[i * prime[j]] = 0, phi[i * prime[j]] = prime[j]; if (i % prime[j] == 0) break; } } return phi; } auto fac = euler_range(M); int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; string s; cin >> s; P[0] = f[0] = 1; for (int i = 1; i <= M - 10; i ++) { P[i] = P[i - 1] * p, f[i] = f[i - 1] + P[i]; } vector<int> a(n + 1); for (int i = 1; i <= n; i ++) { a[i] = s[i - 1] - 'a' + 1; } SegmentTree<Node> tr(a); while (m --) { int op, l, r; cin >> op >> l >> r; if (!op) { char c; cin >> c; tr.modify(1, l, r, c - 'a' + 1); } else { i64 len = r - l + 1, ans = len, idx = op - 1; if (l == r || tr.query(1, l + 1, r).h[idx] == tr.query(1, l, r - 1).h[idx]) { ans = 1; } else { for (; len > 1; len /= fac[len]) { if (tr.query(1, l + ans / fac[len], r).h[idx] == tr.query(1, l, r - ans / fac[len]).h[idx]) ans /= fac[len]; } } cout << ans << '\n'; } } return 0; }
H-聪明且狡猾的恶魔_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
经典的‘海盗分金问题’,结论就是 \(n\) 个人想要分 \(m\) 枚金币的情况下,要满足 \(50\%\) 及以上的支持率,那第一个能获得的最大金币数为 \(m-\frac{n-1}{2}\)。
感兴趣的可以自己搜一下这个思维题。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; void solve() { int x, n; cin >> x >> n; cout << x - (n - 1) / 2 << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
I-马拉松_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
只有经过 \(x\) 再到 \(y\) 才算一次贡献,那么以 \(x\) 为根节点, \(y\) 及其子树节点通过 \(x\) 结点到达除 \(y\) 的这棵子树上的组合就是答案,即 \(sz_y\times (sz_x-sz_{S[y\in S]})\)。
dfs 处理一下子树大小,然后把除 \(x\) 以外的子树用并查集维护到一个连通块里,把 \(y\) 的那个连通块大小减去即可。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; struct DSU { std::vector<int> f, siz; DSU() {} DSU(int n) { init(n); } void init(int n) { f.resize(n); std::iota(f.begin(), f.end(), 0); siz.assign(n, 1); } int find(int x) { while (x != f[x]) { x = f[x] = f[f[x]]; } return x; } bool same(int x, int y) { return find(x) == find(y); } bool merge(int x, int y) { x = find(x); y = find(y); if (x == y) { return false; } siz[x] += siz[y]; f[y] = x; return true; } int size(int x) { return siz[find(x)]; } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, x, y; cin >> n >> x >> y; vector g(n + 1, vector<int>()); for (int i = 1; i < n; i ++) { int u, v; cin >> u >> v; g[u].emplace_back(v); g[v].emplace_back(u); } DSU dsu; dsu.init(n + 1); vector<int> sz(n + 1); auto dfs = [&](auto & self, int u, int fa)->void{ sz[u] = 1; for (auto v : g[u]) { if (v == fa) continue; self(self, v, u); if (u != fa) { dsu.merge(u, v); } sz[u] += sz[v]; } }; dfs(dfs, x, x); i64 a = sz[x], b = sz[y]; for (auto v : g[x]) { if (dsu.same(v, y)) { a -= sz[v]; break; } } cout << a * b << '\n'; return 0; }
J-尖塔第四强的高手_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
\(F\) 函数是个斐波拉契式的递归,所以会增长得很快,当 \(i>24\) 时,\(F_i> 1e5\),注意到这点就会明白每次询问的点数不会超过 \(24\) 个,而计算 \(lca\) 也就带个 \(20\) 的常数,所以直接暴力求即可,复杂度 \(O(20\times 24\times q)\)。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; const int N = 1e5 + 10, M = N - 10; vector<int> f(N); struct LCA { int n; vector<int> dep; vector<vector<int>> e; vector<array<int, 21>> fa; LCA() {} LCA(int n) { dep.resize(n + 1); e.resize(n + 1); fa.resize(n + 1); } void add(int u, int v) { e[u].push_back(v); e[v].push_back(u); } //计算深度,处理各点祖先 void dfs(int u, int father) { dep[u] = dep[father] + 1; fa[u][0] = father; for (int i = 1; i < 20; i ++) fa[u][i] = fa[fa[u][i - 1]][i - 1]; for (auto v : e[u]) if (v != father) dfs(v, u); } //最近公共祖先 //两点集并的最近公共祖先为两点几分别的最近公共祖先的最近公共祖先, //即LCA(A∪B) = LCA(LCA(A), LCA(B)); int lca(int u, int v) { if (dep[u] < dep[v]) swap(u, v); for (int i = 19; i >= 0; i --) if (dep[fa[u][i]] >= dep[v]) u = fa[u][i]; if (u == v) return v; for (int i = 19; i >= 0; i --) if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; return fa[u][0]; } //d(u,v) = h(u) + h(v) - 2h(LCA(u,v)); //其中d是树上两点间的距离,h代表某点到树根的距离 int get_dis(int u, int v) { return dep[u] + dep[v] - 2 * dep[lca(u, v)]; } }; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int pos = 0; f[1] = 1, f[2] = 2; for (int i = 3; i <= M; i ++) { f[i] = f[i - 1] + f[i - 2]; if (f[i] > 1e5) { pos = i; break; } } int n, r, q; cin >> n >> r >> q; LCA tr(n); for (int i = 1; i < n; i ++) { int u, v; cin >> u >> v; tr.add(u, v); } tr.dfs(r, 0); while (q--) { int x, k; cin >> x >> k; vector<int> node; while (k <= pos && x + f[k] <= n) { node.emplace_back(x + f[k]); k ++; } if (!node.size()) { cout << 0 << '\n'; continue; } int ans = node[0]; for (int i = 1; i < node.size(); i ++) { ans = tr.lca(ans, node[i]); } cout << ans << '\n'; } return 0; }
K-比赛_河南萌新联赛2024第(四)场:河南理工大学 (nowcoder.com)
思路
暴力做法,枚举 \(i,j,k\) ,但这样是 \(O(n^3)\) ,pass。
分别预处理\(a_i\le a_j,a_i\ge a_j,a_j\le a_k,a_j\ge a_k\),但这样是 \(o(n^2)\),还是 pass。
考虑使用两个权值树状数组维护以上四种关系,但是会加两次 \(a_i=a_j=a_k\) 的,此时需要减去,使用权值线段树也可,复杂度 \(O(nlog n)\)。
代码
#include <bits/stdc++.h> using namespace std; using i64 = long long; template<typename T> struct BIT { #ifndef lowbit #define lowbit(x) (x & (-x)); #endif int n; vector<T> t; BIT () {} BIT (int _n): n(_n) { t.resize(_n + 1); } BIT (int _n, vector<T>& a): n(_n) { t.resize(_n + 1); for (int i = 1; i <= n; ++ i) { t[i] += a[i]; int j = i + lowbit(i); if (j <= n) t[j] += t[i]; } } //单点修改 void update(int i, T x) { while (i <= n) { t[i] += x; i += lowbit(i); } } //区间查询 T sum(int i) { T ans = 0; while (i > 0) { ans += t[i]; i -= lowbit(i); } return ans; } T query(int i, int j) { return sum(j) - sum(i - 1); } //区间修改则存入差分数组,[l, r] + k则update(x,k),update(y+1,-k) //单点查询则直接求前缀和sum(x) //求逆序对 /* iota(d.begin(), d.end(), 0); stable_sort(d.begin(), d.end(), [&](int x, int y) { return a[x] < a[y]; });去重排序 BIT<i64> tree(n); i64 ans = 0; for (int i = 1; i <= n; i ++) { tree.update(d[i], 1); ans += i - tree.sum(d[i]); } */ }; void solve() { int n; cin >> n; vector<int> a(n + 1); for (int i = 1; i <= n; i ++) { cin >> a[i]; } const int N = 1e5 + 10, M = N - 10; BIT<i64> bit1(N), bit2(N); for (int i = 3; i <= n; i ++) { bit2.update(a[i], 1); } bit1.update(a[1], 1); i64 ans = 0; for (int j = 2; j < n; j ++) { if (j > 2) bit2.update(a[j], -1); ans += 1ll * bit1.sum(a[j]) * (bit2.query(a[j], M)); ans += 1ll * bit2.sum(a[j]) * (bit1.query(a[j], M)); ans -= 1ll * bit1.query(a[j], a[j]) * bit2.query(a[j], a[j]); if (j < n - 1) bit1.update(a[j], 1); } cout << ans << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { solve(); } return 0; }
本文作者:Ke_scholar
本文链接:https://www.cnblogs.com/Kescholar/p/18351601
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步