2025牛客暑期多校训练营8
A. Insert One
题意:给你一个数\(x\),你要插一个\(1\)进去,使得这个数尽可能大。
分类讨论。
如果\(x > 0\)则插到第一个\(0\)前面,没有\(0\)则插到最后面。
如果\(x < 0\)则插到第一个大于\(1\)的数前面,没有则插到最后面。
如果\(x = 0\),输出\(10\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 x;
std::cin >> x;
if (x > 0) {
std::string s = std::to_string(x);
for (int i = 0; i < s.size(); ++ i) {
if (s[i] - '0' < 1) {
std::cout << s.substr(0, i) << 1 << s.substr(i) << "\n";
return;
}
}
std::cout << s << "1" << "\n";
} else if (x < 0) {
x = -x;
std::string s = std::to_string(x);
for (int i = 0; i < s.size(); ++ i) {
if (s[i] - '0' > 1) {
std::cout << "-" << s.substr(0, i) << 1 << s.substr(i) << "\n";
return;
}
}
std::cout << "-" << s << "1" << "\n";
} else {
std::cout << 10 << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B. Inversion Number Parity
题意:一个随机生成器生成了一个\(f\)序列。一开始有一个\([0, ... , n -1]\)的排列\(p\)。对于\(i \in [0, n - 1]\),让\(p_i\)和\(p_{i+(f_i\mod (n-i))}\)交换位置。而后生成\(n-1\)次操作,对于\(i \in [1, n - 1]\),有\(l_i = \min(f_{n+3i-3} \mod n, f_{n+3i - 2} \mod n), r_i = \max(f_{n+3i-3} \mod n, f_{n+3i - 2} \mod n), d_i = f_{n+3i - 1} \mod n\)。对于每个操作,让区间\([l_i, r_i]\)左移\(d_i\)次。求一开始和每次操作后的逆序对的奇偶性。
结论是交换两个不同位置的数会使得奇偶性变化,因为对于\(i, j\)这一对交换,只会影响\([i, j]\)之间数对的逆序对。讨论\(k \in [i + 1, j - 1]\)的\(p_k\)和\(p_i, p_j\)产生的逆序对。那么如果\(p_i < p_k <p_j\),逆序对为\(0\),交换后逆序对为\(2\),奇偶性不变。如果\(p_i < p_k > p_j\),逆序对为\(1\),交换后逆序对为也是\(1\),奇偶性不变。其它情况同理。
而\(p_i, p_j\)交换后它们逆序对肯定变化。那么奇偶性改变。
那么对于一开始交换\(p_i\)和\(p_{i+(f_i\mod (n-i))}\)的操作,只要\(f_i \mod (n-i) \ne 0\)逆序对就变化一次,一开始逆序对为\(0\),所以我们可以计算出一开始的逆序对。
对于左移操作,一次左移可以看作\(n-1\)次交换,也就是\(p_l\)一直往右交换直到\(p_r\)。那么可以计算出到底有多少次交换,从而得到奇偶性。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int N = 1e7 + 5;
char ans[N];
void solve() {
int n, A, B, C;
scanf("%d%d%d%d", &n, &A, &B, &C);
i64 U = (1 << 30) - 1;
i64 f3 = A & U, f2 = B & U, f1 = C & U;
i64 f, g, h;
char c = '0';
for (int i = 0; i < n; ++ i) {
g = f3 ^ (((1 << 16) * f3) & U);
h = g ^ (g / (1 << 5));
f = h ^ (2 * h & U) ^ f2 ^ f1;
if (f % (n - i)) {
c ^= 1;
}
f3 = f2;
f2 = f1;
f1 = f;
}
ans[0] = c;
int l, r, d;
for (int i = 1; i < n; ++ i) {
g = f3 ^ (((1 << 16) * f3) & U);
h = g ^ (g / (1 << 5));
f = h ^ (2 * h & U) ^ f2 ^ f1;
f3 = f2;
f2 = f1;
f1 = f;
g = f3 ^ (((1 << 16) * f3) & U);
h = g ^ (g / (1 << 5));
f = h ^ (2 * h & U) ^ f2 ^ f1;
f3 = f2;
f2 = f1;
f1 = f;
g = f3 ^ (((1 << 16) * f3) & U);
h = g ^ (g / (1 << 5));
f = h ^ (2 * h & U) ^ f2 ^ f1;
f3 = f2;
f2 = f1;
f1 = f;
l = std::min(f3 % n, f2 % n);
r = std::max(f3 % n, f2 % n);
d = f1 % n + 1;
d %= (r - l + 1);
ans[i] = ans[i - 1] ^ (d * (r - l & 1) & 1);
}
ans[n] = 0;
puts(ans);
}
int main() {
// std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
scanf("%d", &t);
while (t -- ) {
solve();
}
return 0;
}
C. Bernoulli's Principle
排个序就好。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
i64 H;
std::cin >> n >> H;
std::vector<i64> h(n);
for (int i = 0; i < n; ++ i) {
std::cin >> h[i];
}
std::vector<int> id(n);
std::ranges::iota(id, 0);
std::ranges::sort(id, [&](int i, int j) {
return h[i] * (H - h[i]) < h[j] * (H - h[j]);
});
for (int i = 0; i < n; ++ i) {
std::cout << id[i] + 1 << " \n"[i == n - 1];
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
G. Changing Minimum Spanning Tree
题意:给你一个简单无向图。你要加一条边,边权在\([1, k]\)之间。使得新图也是简单无向图,且有最小生成树,且所有最小生成树都包含你加入的边。问方案数。
考虑\(kruskal\)算法的过程,我们选择一条边作为最小生成树的边,就和合并对应两个的端点所在的联通块,那么后面不会有两个联通块之间的点构成的边被选入最小生成树里了。那么我们可以在两个联通块各选出一个点,给它们连一条边,使得这条边的边权小于当前选择的边的边权。那么这条边就一定在所有最小生成树内。于是我们可以在\(kruskal\)的过程得到方案数。
但这样会有非法的边,因为不能有重边,而我们选择两个联通块的任意两个点连边,可能选择的两个点原本是有边的。那么需要减去这些贡献。考虑\(kruskal\)时建出最小生成树,然后倍增求出每个的祖先和到祖先的最大边权。也就是记\(fa[u][i]\)表示从\(u\)往上爬\(2^i\)步到达的点,\(max[u][i]\)表示\(u\)到\(fa[u][i]\)路径上边的边权最大值。
则可以枚举原来的边\((u, v)\),求出最小生成树它们的路径上的最大边权\(w\),\(u, v\)两个点一定是在枚举的这条边合并到同一个联通块的,那么答案需要减去\(w-1\)。
然后需要注意的是判联通块个数,如果联通块个数大于\(2\)方案为\(0\),如果恰好为\(2\)则就是两个联通块点数乘积乘\(k\)。这个\(kruskal\)时正好用并查集维护。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
template<class T>
constexpr T power(T a, i64 b) {
T res = 1;
for (; b; b /= 2, a *= a) {
if (b % 2) {
res *= a;
}
}
return res;
}
constexpr i64 mul(i64 a, i64 b, i64 p) {
i64 res = a * b - i64(1.L * a * b / p) * p;
res %= p;
if (res < 0) {
res += p;
}
return res;
}
template<i64 P>
struct MLong {
i64 x;
constexpr MLong() : x{} {}
constexpr MLong(i64 x) : x{norm(x % getMod())} {}
static i64 Mod;
constexpr static i64 getMod() {
if (P > 0) {
return P;
} else {
return Mod;
}
}
constexpr static void setMod(i64 Mod_) {
Mod = Mod_;
}
constexpr i64 norm(i64 x) const {
if (x < 0) {
x += getMod();
}
if (x >= getMod()) {
x -= getMod();
}
return x;
}
constexpr i64 val() const {
return x;
}
explicit constexpr operator i64() const {
return x;
}
constexpr MLong operator-() const {
MLong res;
res.x = norm(getMod() - x);
return res;
}
constexpr MLong inv() const {
assert(x != 0);
return power(*this, getMod() - 2);
}
constexpr MLong &operator*=(MLong rhs) & {
x = mul(x, rhs.x, getMod());
return *this;
}
constexpr MLong &operator+=(MLong rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MLong &operator-=(MLong rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MLong &operator/=(MLong rhs) & {
return *this *= rhs.inv();
}
friend constexpr MLong operator*(MLong lhs, MLong rhs) {
MLong res = lhs;
res *= rhs;
return res;
}
friend constexpr MLong operator+(MLong lhs, MLong rhs) {
MLong res = lhs;
res += rhs;
return res;
}
friend constexpr MLong operator-(MLong lhs, MLong rhs) {
MLong res = lhs;
res -= rhs;
return res;
}
friend constexpr MLong operator/(MLong lhs, MLong rhs) {
MLong res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream &operator>>(std::istream &is, MLong &a) {
i64 v;
is >> v;
a = MLong(v);
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const MLong &a) {
return os << a.val();
}
friend constexpr bool operator==(MLong lhs, MLong rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator!=(MLong lhs, MLong rhs) {
return lhs.val() != rhs.val();
}
};
template<>
i64 MLong<0LL>::Mod = i64(1E18) + 9;
template<int P>
struct MInt {
int x;
constexpr MInt() : x{} {}
constexpr MInt(i64 x) : x{norm(x % getMod())} {}
static int Mod;
constexpr static int getMod() {
if (P > 0) {
return P;
} else {
return Mod;
}
}
constexpr static void setMod(int Mod_) {
Mod = Mod_;
}
constexpr int norm(int x) const {
if (x < 0) {
x += getMod();
}
if (x >= getMod()) {
x -= getMod();
}
return x;
}
constexpr int val() const {
return x;
}
explicit constexpr operator int() const {
return x;
}
constexpr MInt operator-() const {
MInt res;
res.x = norm(getMod() - x);
return res;
}
constexpr MInt inv() const {
assert(x != 0);
return power(*this, getMod() - 2);
}
constexpr MInt &operator*=(MInt rhs) & {
x = 1LL * x * rhs.x % getMod();
return *this;
}
constexpr MInt &operator+=(MInt rhs) & {
x = norm(x + rhs.x);
return *this;
}
constexpr MInt &operator-=(MInt rhs) & {
x = norm(x - rhs.x);
return *this;
}
constexpr MInt &operator/=(MInt rhs) & {
return *this *= rhs.inv();
}
friend constexpr MInt operator*(MInt lhs, MInt rhs) {
MInt res = lhs;
res *= rhs;
return res;
}
friend constexpr MInt operator+(MInt lhs, MInt rhs) {
MInt res = lhs;
res += rhs;
return res;
}
friend constexpr MInt operator-(MInt lhs, MInt rhs) {
MInt res = lhs;
res -= rhs;
return res;
}
friend constexpr MInt operator/(MInt lhs, MInt rhs) {
MInt res = lhs;
res /= rhs;
return res;
}
friend constexpr std::istream &operator>>(std::istream &is, MInt &a) {
i64 v;
is >> v;
a = MInt(v);
return is;
}
friend constexpr std::ostream &operator<<(std::ostream &os, const MInt &a) {
return os << a.val();
}
friend constexpr bool operator==(MInt lhs, MInt rhs) {
return lhs.val() == rhs.val();
}
friend constexpr bool operator!=(MInt lhs, MInt rhs) {
return lhs.val() != rhs.val();
}
};
template<>
int MInt<0>::Mod = 998244353;
template<int V, int P>
constexpr MInt<P> CInv = MInt<P>(V).inv();
constexpr int P = 1000000007;
using Z = MInt<P>;
struct DSU {
std::vector<int> fa, cnt;
DSU();
DSU(int n) {
init(n);
}
void init(int n) {
fa.assign(n + 1, 0);
cnt.assign(n + 1, 1);
std::ranges::iota(fa, 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
int u = find(x), v = find(y);
if (u == v) {
return false;
}
fa[v] = u;
cnt[u] += cnt[v];
return true;
}
bool same(int u, int v) {
return find(u) == find(v);
}
int size(int u) {
return cnt[find(u)];
}
};
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::tuple<int, int, int>> edges(m);
DSU dsu(n + 1);
for (int i = 0; i < m; ++ i) {
int u, v, w;
std::cin >> u >> v >> w;
edges[i] = {w, u, v};
}
Z ans = 0;
std::ranges::sort(edges);
std::vector<std::vector<std::pair<int, int>>> adj(n + 1);
for (auto & [w, u, v] : edges) {
if (dsu.same(u, v)) {
continue;
}
ans += (Z)dsu.size(u) * dsu.size(v) * (w - 1);
adj[u].emplace_back(v, w);
adj[v].emplace_back(u, w);
dsu.merge(u, v);
}
const int lg = std::__lg(n) + 1;
std::vector fa(n + 1, std::array<int, 20>{});
std::vector max(n + 1, std::array<int, 20>{});
std::vector<int> d(n + 1);
auto dfs = [&](auto & self, int u) -> void {
for (auto & [v, w] : adj[u]) {
if (v == fa[u][0]) {
continue;
}
d[v] = d[u] + 1;
fa[v][0] = u;
max[v][0] = w;
for (int i = 1; i <= lg; ++ i) {
fa[v][i] = fa[fa[v][i - 1]][i - 1];
max[v][i] = std::max(max[v][i - 1], max[fa[v][i - 1]][i - 1]);
}
self(self, v);
}
};
d[1] = 1;
dfs(dfs, 1);
auto get = [&](int u, int v) -> int {
if (d[u] < d[v]) {
std::swap(u, v);
}
int res = 0;
for (int i = lg; i >= 0; -- i) {
if (d[fa[u][i]] >= d[v]) {
res = std::max(res, max[u][i]);
u = fa[u][i];
}
}
if (u == v) {
return res;
}
for (int i = lg; i >= 0; -- i) {
if (fa[u][i] != fa[v][i]) {
res = std::max({res, max[u][i], max[v][i]});
u = fa[u][i];
v = fa[v][i];
}
}
return std::max({res, max[u][0], max[v][0]});
};
for (auto & [w, u, v] : edges) {
ans -= get(u, v) - 1;
}
std::vector<int> a;
for (int i = 1; i <= n; ++ i) {
if (dsu.find(i) == i) {
a.push_back(dsu.size(i));
}
}
if (a.size() > 2) {
std::cout << 0 << "\n";
} else if (a.size() == 2) {
std::cout << (Z)a[0] * a[1] * k << "\n";
} else {
std::cout << ans << "\n";
}
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}
J. Multiplication in Base the Square Root of -2
题意:一个\(01\)串\(s\)表示一个\(\sqrt{-2}\)进制下的数。\(s_i = \sqrt{-2}^i\)。给出两个\(01\)串\(a, b\),求它们\(\sqrt{-2}\)进制的乘积。
可以发现能用高精度乘法算。但朴素的高精度乘法是\(n^2\)的。记\(x = \sqrt{-2}\),那么发现每个数可以表示为\(t_0x^0 + t_1x^1 + t_2x^2...\)这是一个多项式,可以用快速傅里叶变换求。
我也没学过快速傅里叶,网上拿了个\(ntt\)的板子。
然后我们求得的结果的系数不是二进制的,发现\(2\sqrt{-2}^i = -\sqrt{-2}^2 \sqrt{-2}^{i-2}\),那么可以拿\(i\)和\(i+2\)的系数抵消,如果还有剩下的,发现\(2\sqrt{-2}^i = (\sqrt{-2}^2 + \sqrt{-2}^4)\sqrt{-2}^i\),那么可以加到\(i+2\)和\(i+4\)上面。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
const int MOD = 998244353;
const int G = 3;
int mod_pow(int a, int e = MOD - 2) {
i64 r = 1, x = a;
while (e) {
if (e & 1) r = (r * x) % MOD;
x = (x * x) % MOD;
e >>= 1;
}
return int(r);
}
void ntt(std::vector<int> & a, bool invert) {
int n = a.size();
for (int i = 1, j = 0; i < n; i++) {
int bit = n >> 1;
for (; j & bit; bit >>= 1) j ^= bit;
j |= bit;
if (i < j) std::swap(a[i], a[j]);
}
for (int len = 2; len <= n; len <<= 1) {
int wlen = mod_pow(G, (MOD - 1) / len);
if (invert) {
wlen = mod_pow(wlen);
}
for (int i = 0; i < n; i += len) {
i64 w = 1;
for (int j = 0; j < len/2; j++) {
int u = a[i + j];
int v = int((a[i + j + len/2] * w) % MOD);
a[i + j] = u + v < MOD ? u + v : u + v - MOD;
a[i + j + len/2] = u - v >= 0 ? u - v : u - v + MOD;
w = (w * wlen) % MOD;
}
}
}
if (invert) {
int inv_n = mod_pow(n);
for (int & x : a)
x = int((i64)x * inv_n % MOD);
}
}
std::vector<int> multiply(const std::vector<int> & a, const std::vector<int> & b) {
int na = a.size(), nb = b.size();
int n = 1;
while (n < na + nb - 1) n <<= 1;
std::vector<int> fa(n), fb(n);
for (int i = 0; i < na; i++) fa[i] = a[i];
for (int i = 0; i < nb; i++) fb[i] = b[i];
ntt(fa, false);
ntt(fb, false);
for (int i = 0; i < n; i++)
fa[i] = int((i64)fa[i] * fb[i] % MOD);
ntt(fa, true);
fa.resize(na + nb - 1);
return fa;
}
void solve() {
std::string s, t;
std::cin >> s >> t;
int n = s.size(), m = t.size();
std::vector<int> a(n), b(m);
for (int i = 0; i < n; ++ i) {
a[i] = s[i] - '0';
}
for (int i = 0; i < m; ++ i) {
b[i] = t[i] - '0';
}
std::ranges::reverse(a);
std::ranges::reverse(b);
auto c = multiply(a, b);
int k = n + m + n + m;
while (c.size() < k) {
c.push_back(0);
}
for (int i = 0; i + 2 < k; ++ i) {
i64 t = std::min(c[i + 2], c[i] / 2);
c[i + 2] -= t;
c[i] -= t * 2;
c[i + 2] += c[i] / 2;
c[i + 4] += c[i] / 2;
c[i] %= 2;
}
while (c.size() > 1 && c.back() == 0) {
c.pop_back();
}
std::ranges::reverse(c);
for (auto & x : c) {
std::cout << x;
}
std::cout << "\n";
}
int main() {
std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
int t = 1;
std::cin >> t;
while (t -- ) {
solve();
}
return 0;
}