2023 ICPC HIAST Collegiate Programming Contest
因为补题的时候,发现网上找不到一篇题解(补题补的很是痛苦),所以写了一篇,希望能帮助之后补这场比赛的人~~~
有些太简单签到就没写,还有
A. Gym Plates
解题思路
比较裸的一个状压 DP,我们考虑把数字的选取次数压到 DP 里面去,显然
转移的时候维护好
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int pw[20];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
pw[0] = 1;
for (int i = 1; i <= 10; i++) {
pw[i] = pw[i - 1] * 3;
}
while (t--) {
int n;
cin >> n;
vector<LL> w(n + 1);
for (int i = 1; i <= n; i++) {
cin >> w[i];
}
vector<LL> dp(pw[10], -1);
dp[0] = 0;
for (int i = 0; i < n; i++) {
vector<LL> ndp(pw[10], -1);
for (int j = 0; j < pw[10]; j++) {
if (dp[j] == -1) continue;
ndp[j] = max(ndp[j], dp[j]);
vector<int> cnt(10, 0);
LL v = w[i + 1];
while (v) {
cnt[v % 10]++;
v /= 10;
}
int state = 0;
bool ok = true;
for (int k = 9; k >= 0; k--) {
int p = j / pw[k] % 3;
if (p + cnt[k] > 2) {
ok = false;
break;
}
state = state * 3 + p + cnt[k];
}
if (ok) {
ndp[state] = max(ndp[state], dp[j] + w[i + 1]);
}
}
dp = ndp;
}
cout << *max_element(dp.begin(), dp.end()) << "\n";
}
return 0;
}
B. Convarge To 1
解题思路
首先我们设
然后问题变成了我们如何求出每一个
我们考虑优化堆的这个
而且我们发现,当一个数变为质数了,那么下一次它一步一定会变到
序,此时我们在对
我们来分析一下这个复杂度,首先我们不管排序,我们知道每个数最多被除
在自身变成质数的时候才会进行排序,所以我们知道了每个数最多被排序一次,所以这一块的复杂度也是
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e6 + 10;
int mip[N], a[N], t[N], lg[N];
int f[N][22];
vector<int> g[N];
void init(int n) {
for (int i = 2; i <= n; i++) {
if (!mip[i]) {
mip[i] = i;
for (int j = 2 * i; j <= n; j += i) {
mip[j] = i;
}
}
}
}
void init_st(int n) {
for (int j = 0; j <= lg[n]; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
if (!j) f[i][j] = t[i];
else f[i][j] = max(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
}
}
int query(int l, int r) {
int k = lg[r - l + 1];
return max(f[l][k], f[r - (1 << k) + 1][k]);
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n, q;
cin >> n >> q;
int mx = 0;
for (int i = 1; i <= n; i++) {
if (i > 1) lg[i] = lg[i / 2] + 1;
cin >> a[i];
mx = max(mx, a[i]);
g[a[i]].push_back(i);
}
init(mx);
int tot = 0;
for (int i = mx; i >= 2; i--) {
if (g[i].size() == 0) continue;
if (mip[i] == i) {
sort(g[i].begin(), g[i].end());
}
for (auto v : g[i]) {
tot++;
if (mip[i] == i) t[v] = tot;
else {
g[i / mip[i]].push_back(v);
}
}
}
init_st(n);
while (q--) {
int l, r;
cin >> l >> r;
cout << query(l, r) << "\n";
}
return 0;
}
C. Tree Permutation
解题思路
首先这类期望题,一眼看上去就是 DP 不了的,我们考虑推公式或者直接算贡献。
我们发现答案其实就是
我们考虑一下如何计算
那么剩下的
故求
所以问题被转化为一个经典的问题,求树上任意两点的距离之和,这是一个经典的换根 DP。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
vector<int> g[N];
LL dp[N];
int sz[N];
void dfs1(int u, int fa) {
dp[u] = 0, sz[u] = 1;
for (auto v : g[u]) {
if (v == fa) continue;
dfs1(v, u);
sz[u] += sz[v];
dp[u] += dp[v];
}
dp[u] += sz[u] - 1;
}
void dfs2(int u, int fa) {
for (auto v : g[u]) {
if (v == fa) continue;
dp[v] = dp[u] + sz[1] - 2 * sz[v];
dfs2(v, u);
}
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) g[i].clear();
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1, 0);
dfs2(1, 0);
double ans = 0;
for (int i = 1; i <= n; i++) {
ans += dp[i];
}
cout << fixed << setprecision(12) << ans / n << "\n";
}
return 0;
}
H. Yaser In Baradah
解题思路
直接用 set + multiset 维护就好了。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
set<int> pos;
multiset<LL> val;
vector<LL> vgn(n + 1);
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
vgn[i] = x;
pos.insert(i);
val.insert(x);
}
cout << *val.rbegin() << "\n";
int Q;
cin >> Q;
while (Q--) {
int s;
cin >> s;
auto it = pos.upper_bound(s);
vgn[*it] += vgn[s];
pos.erase(pos.find(s));
val.insert(vgn[*it]);
cout << *val.rbegin() << "\n";
}
}
return 0;
}
J. Completely Balanced
解题思路
如果我们知道了数组最后的中位数,那么
我们考虑加入一个数之后,数组的中位数可以是哪些数。
- 是原先数组中的数:把数组排序,那么
, 都有可能成为中位数。 - 是新加入的那个数
。
我们只要依次检查这三种情况,取最小的 即可。
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<LL> a(n + 1);
LL sum = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
sum += a[i];
}
n += 1;
sort(a.begin() + 1, a.end());
// 1. 考虑中位数是原先的数字
int mid = (n + 1) / 2;
// 那么 x, a[mid] , a[mid - 1] 都有可能成为中位数
LL ans = 2e18;
LL x1 = (n * a[mid] - sum);
auto check = [&](LL x, LL y) {
vector<LL> b = a;
b.push_back(x);
sort(b.begin() + 1, b.end());
return b[mid] == y && (sum + x) % n == 0;
};
if (check(x1, a[mid])) ans = min(ans, x1);
if (mid - 1 >= 1) {
LL x2 = (n * a[mid - 1] - sum);
if (check(x2, a[mid - 1])) ans = min(ans, x2);
}
if (sum % (n - 1) == 0) {
LL x = sum / (n - 1);
if (check(x, x)) ans = min(ans, x);
}
cout << ans << "\n";
}
return 0;
}
K. Sam-Oh, the funny coach
解题思路
我们可以对于每一个字符串都维护一个长度最多为
询问的时候,依次让两个字符串所在的区间求交,即可算出答案。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
vector<array<int, 3>> g[N];
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
string s;
cin >> s;
for (int j = 0; j < m; j++) {
int p = j;
while (p < m && s[j] == s[p]) p++;
g[i].push_back({j + 1, p, s[j] - 'a'});
j = p - 1;
}
}
int Q;
cin >> Q;
while (Q--) {
int p1, p2;
cin >> p1 >> p2;
vector<array<int, 3>> &it1 = g[p1], &it2 = g[p2];
vector<array<int, 2>> cur[26];
for (auto &[l, r, t] : it1) {
cur[t].push_back({l, r});
}
int ans = 0;
for (auto &[l, r, t] : it2) {
if (cur[t].size()) {
int L = cur[t][0][0], R = cur[t][0][1];
ans += max(0, min(R, r) - max(L, l) + 1);
}
}
cout << ans << "\n";
}
return 0;
}
L. Trip Discount
解题思路
首先考虑
现在问题变成了,我们要选择
我们设
对于
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL, int> PLI;
const int N = 1e4 + 10, M = 1010;
const LL INF = 1e18;
vector<array<int, 2>> g[N];
int dep[N], f[N][16], d[N], val[N], sz[N];
LL dp[N][M];
int n, k, m;
void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
for (auto &[v, w] : g[u]) {
if (v == fa) continue;
f[v][0] = u;
val[v] = w;
for (int i = 1; i <= 15; i++) {
f[v][i] = f[f[v][i - 1]][i - 1];
}
dfs(v, u);
}
}
void self(int u, int fa) {
for (auto &[v, w] : g[u]) {
if (v == fa) continue;
self(v, u);
d[u] += d[v];
}
}
int LCA(int a, int b) {
if (dep[a] < dep[b]) swap(a, b);
for (int i = 15; i >= 0; i--) {
if (dep[f[a][i]] >= dep[b]) {
a = f[a][i];
}
}
if (a == b) return a;
for (int i = 15; i >= 0; i--) {
if (f[a][i] != f[b][i]) {
a = f[a][i];
b = f[b][i];
}
}
return f[a][0];
}
void DFS(int u, int fa) {
sz[u] = 1;
dp[u][0] = dp[u][1] = 0;
for (auto [v, w] : g[u]) {
if (v == fa) continue;
DFS(v, u);
LL cost = 1LL * d[v] * w;
static LL tmp[M];
for (int i = 0; i <= min(k, sz[u] + sz[v]); i++) tmp[i] = -INF;
for (int i = 0; i <= min(k, sz[u]); i++) {
for (int j = 0; j <= sz[v] && i + j <= k; j++) {
tmp[i + j] = max(tmp[i + j], dp[u][i] + dp[v][j] + (j && j != k) * cost);
}
}
sz[u] += sz[v];
for (int i = 0; i <= min(k, sz[u]); i++) {
dp[u][i] = tmp[i];
}
}
}
void solve() {
cin >> n >> k >> m;
for (int i = 1; i <= n; i++) g[i].clear(), d[i] = val[i] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= k; j++) {
dp[i][j] = -INF;
}
}
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
dfs(1, 0);
while (m--) {
int u, v;
cin >> u >> v;
d[u]++, d[v]++, d[LCA(u, v)] -= 2;
}
self(1, 0);
LL ans = 0;
for (int i = 1; i <= n; i++) {
ans += 1LL * val[i] * d[i];
}
DFS(1, 0);
cout << ans - dp[1][k] << "\n";
}
int main() {
cin.tie(nullptr)->sync_with_stdio(false);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现