2025牛客寒假算法基础集训营4
A. Tokitsukaze and Absolute Expectation
题意:
可以单独求出每个位置和前面位置的的期望再相加。
那么问题变成了给你两个区间,求它们差的绝对值的期望。
如果两个区间不相交,假设
然后考虑重叠的情况,我们可以先把重叠的区间算出来,然后重叠区间的左边与其右边的贡献,它们是不相交的可以直接算。同理计算重叠区间右边与其左边的贡献,这两部分会有一段区间重叠,注意一下范围。然后考虑怎么算重叠的区间的贡献,打表发现是一个对称的矩阵,第
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> l(n), r(n);
for (int i = 0; i < n; ++ i) {
std::cin >> l[i] >> r[i];
}
auto calc = [&](int l1, int r1, int l2, int r2) -> Z {
if (l1 > r1 || l2 > r2) {
return 0;
}
Z n = r2 - l2 + 1, m = r1 - l1 + 1;
Z a = n * (l2 - l1 + r2 - l1) / 2, an = a - n * (m - 1);
Z res = m * (a + an) / 2;
return res;
};
Z ans = 0;
for (int i = 1; i < n; ++ i) {
int x = std::max(l[i - 1], l[i]), y = std::min(r[i - 1], r[i]);
Z sum = 0;
if (x <= y) {
int len = y - x + 1;
sum += (Z)len * (len + 1) * (len - 1) / 3;
}
if (l[i - 1] <= l[i]) {
sum += calc(l[i - 1], std::min(r[i - 1], x - 1), l[i], r[i]);
} else {
sum += calc(l[i], std::min(r[i], x - 1), l[i - 1], r[i - 1]);
}
if (r[i - 1] <= r[i]) {
sum += calc(x, r[i - 1], std::max(l[i], y + 1), r[i]);
} else {
sum += calc(x, r[i], std::max(l[i - 1], y + 1), r[i - 1]);
}
ans += sum / ((Z)(r[i] - l[i] + 1) * (r[i - 1] - l[i - 1] + 1));
}
std::cout << ans << "\n";
}
B. Tokitsukaze and Balance String (easy) && C. Tokitsukaze and Balance String (hard)
题意:给你一个01串,有些地方是'?'表示没有填。一个01串的价值看它有多少位置满足取反后整个串10和01的个数相等。求所有可能的01串的价值之和。
这题正解是找规律分类讨论,赛时没多想写了个dp。
手玩一下发现,中间的位置不管怎么变都不会影响01串的平衡性。于是如果这个01串本来就是平衡的则贡献为
讲讲我的dp解法,
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::vector f(n + 1, std::array<std::array<Z, 3>, 2>{});
if (s[0] == '?') {
f[1][0][1] = f[1][1][1] = 1;
} else if (s[0] == '0') {
f[1][0][1] = 1;
} else {
f[1][1][1] = 1;
}
for (int i = 1; i < n; ++ i) {
if (s[i] != '0') {
f[i + 1][1][0] = f[i][1][0] + f[i][0][1];
f[i + 1][1][1] = f[i][1][1] + f[i][0][2];
f[i + 1][1][2] = f[i][1][2];
}
if (s[i] != '1') {
f[i + 1][0][0] = f[i][0][0];
f[i + 1][0][1] = f[i][0][1] + f[i][1][0];
f[i + 1][0][2] = f[i][0][2] + f[i][1][1];
}
}
if (n == 1) {
std::cout << (f[n][0][1] + f[n][1][1]) << "\n";
return;
}
Z ans = (f[n][0][1] + f[n][1][1]) * (Z)(n - 2);
if (s[0] != '0') {
ans += f[n][0][2] + f[n][1][2];
}
if (s[0] != '1') {
ans += f[n][0][0] + f[n][1][0];
}
if (s.back() != '0') {
ans += f[n][0][0] + f[n][1][0];
}
if (s.back() != '1') {
ans += f[n][0][2] + f[n][1][2];
}
std::cout << ans << "\n";
}
D. Tokitsukaze and Concatenate Palindrome
题意:给你两个字符串
因为可以重新排列,那么我们尽可能把相同的字符放到对应的位置。然后长串是可以放一些字符到另一边自己和自己匹配的,于是就是统计一下有多少个字符不能匹配,直接除二。因为两边不能匹配的字符数肯定是相等的。如果是奇数个,那么
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::string s, t;
std::cin >> s >> t;
std::array<int, 26> cnt{};
for (auto & c : s) {
cnt[c - 'a'] += 1;
}
for (auto & c : t) {
cnt[c - 'a'] -= 1;
}
int ans = 0;
int tot = std::max(n, m) - (n + m + 1) / 2;
for (int i = 0; i < 26; ++ i) {
if (cnt[i] < 0 && n <= m) {
cnt[i] = -cnt[i];
int t = std::min(tot, cnt[i] / 2);
cnt[i] -= t * 2;
tot -= t;
} else if (cnt[i] > 0 && n >= m) {
int t = std::min(tot, cnt[i] / 2);
cnt[i] -= t * 2;
tot -= t;
}
}
for (int i = 0; i < 26; ++ i) {
ans += std::abs(cnt[i]);
}
std::cout << ans / 2 << "\n";
}
E. Tokitsukaze and Dragon's Breath
题意:给你一个矩阵,可以选择一个位置得到这个位置所处主对角线和次对角线上所有元素的和。求最大值。
每个主对角线的
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector a(n, std::vector<int>(m));
std::vector<i64> sum1((n + m) * 2), sum2((n + m) * 2);
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
std::cin >> a[i][j];
sum1[i + j + n + m] += a[i][j];
sum2[i - j + n + m] += a[i][j];
}
}
i64 ans = 0;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < m; ++ j) {
ans = std::max(ans, sum1[i + j + n + m] + sum2[i - j + n + m] - a[i][j]);
}
}
std::cout << ans << "\n";
}
F. Tokitsukaze and Kth Problem (easy)
题意:求
做法一:
先把数组每个数对
考虑二分求第k值,每次看
求出第
最后一直加第
参考了题解代码。
点击查看代码
void solve() {
int n, p, m;
std::cin >> n >> p >> m;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
a[i] %= p;
}
std::sort(a.begin(), a.end());
auto get = [&](int x) -> i64 {
i64 res = 0;
for (int i = 0, j = n - 1; i < n; ++ i) {
while (j >= 0 && a[i] + a[j] >= x) {
-- j;
}
res += n - 1 - std::max(i, j);
}
return res;
};
i64 getp = get(p);
auto check = [&](int x) -> i64 {
return get(x) - getp + get(x + p);
};
int l = 0, r = p - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid) >= m) {
l = mid;
} else {
r = mid - 1;
}
}
if (check(l) < m) {
l = -1;
}
std::vector<int> ans;
for (int i = 0, j = n - 1, k = n - 1; i < n; ++ i) {
while (j >= 0 && a[i] + a[j] > l) {
-- j;
}
while (k >= 0 && a[i] + a[k] > l + p) {
-- k;
}
for (int x = std::max(i, j) + 1; x < n && a[i] + a[x] < p; ++ x) {
ans.push_back(a[i] + a[x]);
}
for (int x = std::max(i, k) + 1; x < n; ++ x) {
ans.push_back((a[i] + a[x]) % p);
}
}
while (ans.size() < m) {
ans.push_back(l);
}
std::sort(ans.begin(), ans.end(), std::greater<int>());
for (int i = 0; i < m; ++ i) {
std::cout << ans[i] << " \n"[i == m - 1];
}
}
做法2:
一样的分成两种情况考虑,
点击查看代码
void solve() {
int n, P, m;
std::cin >> n >> P >> m;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
a[i] %= P;
}
std::sort(a.begin(), a.end());
std::vector<int> p(n), p1(n), p2(n);
std::priority_queue<std::pair<int, int>> heap;
for (int i = 0; i + 1 < n; ++ i) {
int j = std::lower_bound(a.begin(), a.end(), P - a[i]) - a.begin() - 1;
p[i] = p1[i] = std::max(j, i);
if (j > i) {
heap.push({(a[i] + a[j]) % P, i});
}
p2[i] = n - 1;
if (p2[i] > p[i]) {
heap.push({(a[i] + a[n - 1]) % P, i});
}
}
std::vector<int> ans;
while ((int)ans.size() < m && heap.size()) {
auto [x, i] = heap.top(); heap.pop();
ans.push_back(x);
if (p1[i] > i && (a[i] + a[p1[i]]) % P == x) {
-- p1[i];
if (p1[i] > i) {
heap.push({(a[i] + a[p1[i]]) % P, i});
}
} else {
-- p2[i];
if (p2[i] > p[i]) {
heap.push({(a[i] + a[p2[i]]) % P, i});
}
}
}
while (ans.size() < m) {
ans.push_back(-1);
}
for (int i = 0; i < m; ++ i) {
std::cout << ans[i] << " \n"[i == m - 1];
}
}
G. Tokitsukaze and Kth Problem (hard)
待补
H. Tokitsukaze and Necklace
题意:你有一些'a', 'b', 'c'。这三个字母可以有27种排列,每种排列都有自己的贡献,你要用它们构造一个序列,序列首尾相连,其值等于每三个相邻字母的值。求最大值及方案。
赛时没看这题,dp思路还是很简单的。
记录方案也很麻烦,但dp记方案的套路是固定的,记录每个状态是由哪个状态过来的,从后往前推出来就行。因为有环,可以枚举前两位放什么来破环。思路倒不是很难,具体看代码。
点击查看代码
const int N = 3;
int val[N][N][N];
int cnt[N];
i64 f[151][151][151][3][3];
std::array<int, 4> pre[151][151][151][3][3] = {};
void solve() {
int n, m;
std::cin >> n >> m;
memset(val, 0, sizeof val);
memset(cnt, 0, sizeof cnt);
std::string s;
std::cin >> s;
for (auto & c : s) {
++ cnt[c - 'a'];
}
for (int i = 0; i < m; ++ i) {
std::string s;
int v;
std::cin >> s >> v;
val[s[0] - 'a'][s[1] - 'a'][s[2] - 'a'] = v;
}
std::string ans = s;
const i64 inf = 1e18;
i64 max_val = -inf;
auto work = [&](int a, int b) -> void {
std::array<int, 3> tmp{};
tmp[a] += 1;
tmp[b] += 1;
for (int i = 0; i < 3; ++ i) {
if (tmp[i] > cnt[i]) {
return;
}
}
for (int i = 1; i <= n; ++ i) {
for (int j = 0; j <= cnt[0] && j <= i; ++ j) {
for (int k = 0; k <= cnt[1] && j + k <= i; ++ k) {
if (i - j - k <= n - cnt[0] - cnt[1]) {
for (int x = 0; x < 3; ++ x) {
for (int y = 0; y < 3; ++ y) {
f[i][j][k][x][y] = -inf;
}
}
}
}
}
}
auto calc = [&](int i, int a, int b, int c, int d, int x, int y, int z) -> void {
if (c < 0 || d < 0) {
return;
}
if (a == c && b == d && i - a - b <= 0) {
return;
}
if (f[i - 1][c][d][y][z] + val[z][y][x] <= f[i][a][b][x][y]) {
return;
}
f[i][a][b][x][y] = f[i - 1][c][d][y][z] + val[z][y][x];
pre[i][a][b][x][y] = {c, d, y, z};
};
f[2][tmp[0]][tmp[1]][b][a] = 0;
for (int i = 3; i <= n; ++ i) {
for (int j = 0; j <= cnt[0] && j <= i; ++ j) {
for (int k = 0; k <= cnt[1] && j + k <= i; ++ k) {
if (i - j - k > n - cnt[0] - cnt[1]) {
continue;
}
for (int x = 0; x < 3; ++ x) {
for (int y = 0; y < 3; ++ y) {
calc(i, j, k, j - 1, k, 0, x, y);
calc(i, j, k, j, k - 1, 1, x, y);
calc(i, j, k, j, k, 2, x, y);
}
}
}
}
}
i64 max = -inf;
std::array<int, 4> end{};
for (int x = 0; x < 3; ++ x) {
for (int y = 0; y < 3; ++ y) {
i64 v = f[n][cnt[0]][cnt[1]][x][y] + val[y][x][a] + val[x][a][b];
if (v > max) {
max = v;
end = {cnt[0], cnt[1], x, y};
}
}
}
if (max > max_val) {
max_val = max;
ans.clear();
for (int i = n; i > 2; -- i) {
// std::cerr << end[0] << " " << end[1] << " " << end[2] << " " << end[3] << "\n";
ans += char(end[2] + 'a');
end = pre[i][end[0]][end[1]][end[2]][end[3]];
}
ans += char(b + 'a');
ans += char(a + 'a');
}
};
for (int a = 0; a < 3; ++ a) {
for (int b = 0; b < 3; ++ b) {
work(a, b);
}
}
std::reverse(ans.begin(), ans.end());
std::cout << max_val << "\n";
std::cout << ans << "\n";
}
I. Tokitsukaze and Pajama Party
题意:求每个"uwawauwa"前面有多少"u"。
模拟统计即可。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
std::string t = "uwawauwa";
int len = t.size();
i64 ans = 0, cnt = 0;
for (int i = 0; i + len - 1 < n; ++ i) {
if (s.substr(i, len) == t) {
ans += cnt;
}
if (i > 0 && s[i - 1] == 'u') {
++ cnt;
}
}
std::cout << ans << "\n";
}
J. Tokitsukaze and Recall
题意:有若干个联通块,你可以选
首先求出所有连通块。设有
要占领尽可能的点,那么我们肯定先联通块大小从大到小的给每个联通块一个传送点,如果大小相同,则最小点更小的优先。然后可以搜索方案,我们给每个联通块最小的点一个传送点,然后我们用优先级队列来搜索使得我们每次可以走编号最小的点。如果还有多余的传送点,并且当前到的点不是可以到的剩余点里的最小的,那么可以给这个剩余点里最小的点一个传送点。模拟即可。
点击查看代码
struct DSU {
std::vector<int> fa, cnt;
DSU(int _n) {
init(_n);
}
void init(int _n) {
fa.assign(_n + 1, 0);
cnt.assign(_n + 1, 1);
std::iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
bool merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) {
return false;
}
fa[y] = x;
cnt[x] += cnt[y];
return true;
}
bool same(int x, int y) {
return find(x) == find(y);
}
int size(int x) {
return cnt[find(x)];
}
};
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::vector<int>> adj(n);
DSU d(n);
for (int i = 0; i < m; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
if (u > v) {
std::swap(u, v);
}
adj[u].push_back(v);
adj[v].push_back(u);
d.merge(u, v);
}
std::vector<std::vector<int>> blocks(n);
int cnt = 0;
for (int i = 0; i < n; ++ i) {
blocks[d.find(i)].push_back(i);
cnt += d.find(i) == i;
}
std::sort(blocks.begin(), blocks.end(), [&](std::vector<int> & a, std::vector<int> & b) {
if (a.size() && b.size() && a.size() == b.size()) {
return a[0] < b[0];
}
return a.size() > b.size();
});
int t = std::min(k, cnt);
k = std::max(0, k - cnt);
std::priority_queue<int, std::vector<int>, std::greater<int> > heap;
std::vector<int> st(n);
std::set<int> s;
for (int i = 0; i < t; ++ i) {
for (auto & x : blocks[i]) {
s.insert(x);
}
heap.push(blocks[i][0]);
st[blocks[i][0]] = 1;
}
std::vector<int> ans;
while (heap.size()) {
if (heap.top() != *s.begin() && k > 0) {
-- k;
heap.push(*s.begin());
st[*s.begin()] = 1;
}
int u = heap.top(); heap.pop();
s.erase(u);
ans.push_back(u);
for (auto & v : adj[u]) {
if (!st[v]) {
st[v] = 1;
heap.push(v);
}
}
}
std::cout << ans.size() << "\n";
for (auto & x : ans) {
std::cout << x + 1 << " \n"[x == ans.back()];
}
}
K. Tokitsukaze and Shawarma
签到题。
点击查看代码
void solve() {
int x, y, z, a, b, c;
std::cin >> x >> y >> z >> a >> b >> c;
std::cout << std::max({x * a, y * b, z * c}) << "\n";
}
L. Tokitsukaze and XOR-Triangle
题意:多次询问求一个区间内两两异或和的和。
按位计算,那么就变成了在一个01串里询问一个区间里有多少01和10。
可以前缀和求出来每个位置和它前面的数产生了多少贡献。但求
点击查看代码
void solve() {
int n, q;
std::cin >> n >> q;
std::vector<i64> a(n + 1), b(n + 1);
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
}
for (int i = 1; i <= n; ++ i) {
std::cin >> b[i];
}
std::vector suma(n + 1, std::array<i64, 30>{});
std::vector sumb(n + 1, std::array<i64, 30>{});
for (int i = 1; i <= n; ++ i) {
suma[i] = suma[i - 1];
sumb[i] = sumb[i - 1];
for (int j = 0; j < 30; ++ j) {
suma[i][j] += a[i] >> j & 1;
sumb[i][j] += b[i] >> j & 1;
}
}
std::vector sum(n + 1, std::array<i64, 30>{});
for (int i = 1; i <= n; ++ i) {
sum[i] = sum[i - 1];
for (int j = 0; j < 30; ++ j) {
if (b[i] >> j & 1) {
sum[i][j] += i - suma[i][j];
} else {
sum[i][j] += suma[i][j];
}
}
}
for (int i = 0; i < q; ++ i) {
int l, r;
std::cin >> l >> r;
Z ans = 0;
for (int i = 0; i < 30; ++ i) {
i64 cnta1 = suma[l - 1][i], cnta0 = l - 1 - cnta1;
i64 cntb1 = sumb[r][i] - sumb[l - 1][i], cntb0 = r - l + 1 - cntb1;
ans += (Z)(1ll << i) * (sum[r][i] - sum[l - 1][i] - cnta1 * cntb0 - cnta0 * cntb1);
}
std::cout << ans << "\n";
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战