A
猜结论. 找到最大值和最小值,显然可以把整个序列删的只剩这两个数,那么只要询问的数在这之间就一定可以.
B
脑筋急转弯,非常的 tricky,我不喜欢.
首先若存在两个偶数,那么直接输出即可. 其次若有一个偶数,可以线性枚举与奇数组成的对. 剩下全是奇数的情况我们可以尝试找性质. 若注意到两奇数的差是偶数,所以当 \(a_j \bmod a_i\) 为奇数时,必然有 \(a_j > 2a_i\). 所以我们可以直接暴力枚举前 \(\log V\) 项的数对,若存在里面必然有一对合法.
点击查看代码
#include<bits/stdc++.h>
using namespace std;const int maxn = 1e5 + 10;
int T, n, a[maxn];void solve() {int x = 0, y = 0;cin >> n; for(int i = 1; i <= n; i++) cin >> a[i];for(int i = 1; i <= n; i++) for(int j = i + 1; j <= n; j++) {if(a[j] % a[i] % 2 == 0) {x = a[i], y = a[j];break;}} if(!x) cout << "-1\n"; else cout << x << " " << y << "\n";
}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> T; while(T--) solve();return 0;
}
C
细节略多的贪心,比 CSP-S 2025 T1 复杂. 考场上想了一个比较正确但是限制更加严格的做法,导致难以实现(要写平衡树),没能写出来.
首先我们应该先打 \(c_i>0\) 的怪物,这样不消耗剑甚至可以强化剑. 在这个基础上我们可以按照升序打 \(b_i\),对于两种 \(c_i\) 都是不劣的. 所以我们把所有初始的剑扔到小根堆里面,依次取出尝试打 \(c_i>0\) 的怪,不能打的留下来打 \(c_i=0\) 的怪,可以打的就更新 \(a_i\) 接着打. 剩下所有的剑接着贪心打\(c_i=0\) 的怪,模拟这个过程即可.
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;const int maxn = 2e5 + 10;
int T, n, m, A[maxn], B[maxn], C[maxn];
vector<int> a2;struct node{int b, c;bool operator <(const node x) const{return b > x.b;}
};
priority_queue<node> q1, q2;
priority_queue<int> a1;void cleann() {while(!q1.empty()) q1.pop();while(!q2.empty()) q2.pop();while(!a1.empty()) a1.pop();vector<int>().swap(a2);
}void solve() {cin >> n >> m; cleann();for(int i = 1, a; i <= n; i++) cin >> a, a1.push(-a);for(int i = 1; i <= m; i++) cin >> B[i];for(int i = 1; i <= m; i++) {cin >> C[i];if(C[i]) q1.push((node){B[i], C[i]});else q2.push((node){B[i], C[i]});}int ans = 0;while(!q1.empty()) {int b = q1.top().b, c = q1.top().c; q1.pop();while(-a1.top() < b && !a1.empty()) a2.push_back(-a1.top()), a1.pop();if(a1.empty()) break;int a = -a1.top(); a1.pop(), a1.push(-max(a, c)); ans++;} while(!a1.empty()) a2.push_back(-a1.top()), a1.pop();int p = 0; sort(a2.begin(), a2.end());while(!q2.empty()) {int b = q2.top().b, c = q2.top().c; q2.pop();while(p < a2.size() && a2[p] < b) p++;if(p == a2.size()) break;ans++; p++;} cout << ans << "\n";
}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> T; while(T--) solve();return 0;
}
D
模拟,想清楚怎么实现更简单是首要任务.
我们需要给所有 \(t_i\neq s_i\) 的找到前面最近的一个相等的位置,且由于只能逐位挪动所以匹配不能有交叉. 贪心地从后往前考虑即可简单求出至少要的操作数. 考虑模拟,我们记录下来匹配的位置 \(p_i\),输出方案我们仍然从后往前考虑,若没匹配上就令 \(s_{p_i+1}=s_{p_i}\),并且 \(p_i+=1\),每次操作输出整个 \(s\) 即可.
点击查看代码
#include<bits/stdc++.h>
using namespace std;const int maxn = 1e6 + 10;
int T, n, k; char s[maxn], t[maxn];
int p[maxn];void solve() {int ans = 0;cin >> n >> k >> (s + 1) >> (t + 1);p[n + 1] = n;for(int i = n; i; i--) {p[i] = min(p[i + 1], i);while(p[i] && s[p[i]] != t[i]) p[i]--;ans = max(ans, i - p[i]);}if(!p[1] || ans > k) {cout << "-1\n"; return;}cout << ans << "\n";while(ans--) {for(int i = n; i; i--) if(p[i] < i) s[p[i] + 1] = s[p[i]], p[i]++;for(int i = 1; i <= n; i++) cout << s[i]; cout << "\n";}
}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> T; while(T--) solve();return 0;
}
E
欧拉回路需要把所有奇度点补成偶度点,而编号最小的边听起来很怪,先考虑对这玩意建重构树,在重构树每个 lca 处统计子树内的贡献. 令 \(sz_u\) 表示子树内奇度叶子的个数,每个点只会在某个公共祖先处统计一次贡献,所以我们在 dfs 过程中记录根链的最小边权,每次最小值发生变化时令所有 \(\lfloor {sz_u\over 2}\rfloor\) 对奇度叶子在这里匹配,并更新 \(sz_u\). 可以说明这样匹配一定是最优的.
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;const int maxn = 1e6 + 10, inff = 2e9 + 1;
int T, n, m;
int tot, fa[maxn << 1], val[maxn << 1], d[maxn << 1], sz[maxn << 1]; ll ans = 0;
inline int fd(int u) {return u == fa[u] ? u : fa[u] = fd(fa[u]);}vector<int> e[maxn << 1];
inline void add(int u, int v) {e[u].push_back(v);}void dfs(int u, int w) {sz[u] = u <= n && (d[u] & 1);for(int v : e[u]) {dfs(v, min(w, val[u])); sz[u] += sz[v];}if(val[u] < w) ans += 1ll * sz[u] / 2 * val[u], sz[u] %= 2;
}void init() {ans = 0, tot = n;for(int i = 1; i <= 2 * n; i++) fa[i] = i, vector<int>().swap(e[i]), d[i] = 0;
}
void solve() {cin >> n >> m; init();for(int i = 1, u, v, w; i <= m; i++) {cin >> u >> v >> w; ans += w; d[u]++, d[v]++;u = fd(u), v = fd(v);if(u != v) {val[++tot] = w; fa[u] = fa[v] = tot;add(tot, u), add(tot, v);}else val[u] = min(val[u], w);} dfs(tot, inff);cout << ans << "\n";
}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0);cin >> T; while(T--) solve();return 0;
}
F1
神秘计数.
首先题目等价于对 \(p^{-1}\) 计数,限制变成在 \(u\) 之前的 \(v\) 为 \(u\) 祖先的个数恰好等于 \(a_u\). 考虑从下往上合并,每次往排列中插入 \(u\),那么所有数前面的个数有一个限制. 转换视角,每次插入一个数后缀所有 \(a\) 减去 \(1\),最终序列合法即序列为 \(0\).
一个重要的观察是,中间序列合法的充要条件是单调不降. 于是每次 \(a_u\) 插入的位置是确定的,即最后一个 \(\le a_u\) 的位置后面,所以方案数只由序列合并的顺序决定,最终的排列 \(p\) 是唯一的.
考虑计数,设 \(f_u\) 为子树 \(u\) 的方案数,记录 \(cnt_{u,i}\) 表示子树 \(u\) 的序列中 \(i\) 的个数. \(u,v\) 合并时对于每种 \(i\) 都要乘上归并的系数 \({cnt_{u,i}+cnt_{v,i}\choose cnt_{u,i}}\). 即对 \(u\) 的每个儿子 \(v\) 有转移:
\(cnt_{u,i}\) 的维护可以根据上面的充要条件做一做.
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;const int maxn = 5e3 + 10, maxv = 5e3, mo = 998244353;
int T, n, a[maxn];
int f[maxn], c[maxn][maxn];
int fac[maxn], ifac[maxn];vector<int> e[maxn];ll qpow(ll x, ll y) {ll res = 1;while(y) {if(y & 1) (res *= x) %= mo;y >>= 1, (x *= x) %= mo;} return res;
}
void init() {fac[0] = ifac[0] = 1;for(int i = 1; i <= maxv; i++) fac[i] = 1ll * fac[i - 1] * i % mo;ifac[maxv] = qpow(fac[maxv], mo - 2);for(int i = maxv - 1; i >= 1; i--) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mo;
}
int C(int x, int y) {return y < 0 || x < y ? 0 : 1ll * fac[x] * ifac[x - y] % mo * ifac[y] % mo;}void dfs(int u) {f[u] = 1;for(int i = 0; i < n; i++) c[u][i] = 0;for(int v : e[u]) {dfs(v); f[u] = 1ll * f[u] * f[v] % mo;for(int j = 0; j < n; j++) f[u] = 1ll * f[u] * C(c[u][j] + c[v][j], c[u][j]) % mo, c[u][j] += c[v][j];} for(int i = a[u] + 1; i < n; i++) c[u][i - 1] += c[u][i], c[u][i] = 0;c[u][a[u]]++;
}void solve() {cin >> n; for(int i = 1; i <= n; i++) vector<int>().swap(e[i]);for(int i = 2, fa; i <= n; i++) cin >> fa, e[fa].push_back(i);for(int i = 1; i <= n; i++) cin >> a[i];dfs(1); cout << f[1] << "\n";
}int main() {ios :: sync_with_stdio(false); cin.tie(0); cout.tie(0); init();cin >> T; while(T--) solve();return 0;
}