补题记录
前言
马上CSP了,才发现之前好几次比赛的题目都没补。。。得赶紧加训了,顺便写一篇博客记录一下。
[CSP-S 2024] 超速检测
Solution
先考虑问题一,首先,对于每一辆车而言,由于 $v_i,a_i$ 都是定值,所以车辆的速度一定是 单调递增或单调递减 的。(当然也可能是定值)。那么每一辆车超速的范围一定是一段区间,且这段区间可以直接求出来。那么对于每一辆车都可以得到一个对应的区间 $[l_i,r_i]$。那么只要区间范围内存在测速点,该车就会被判定为超速。
那么第一问就可以通过二分判断区间内是否存在测速点解决。
之后考虑问题二,在求出所有的区间 $[l_i,r_i]$ 之后,问题就变成了求解最多需要保留几个测速点,使得对于所有区间 $[l_i,r_i]$,都存在一个测速点。
那么这个问题很显然就是线段覆盖问题,考虑贪心。将区间的右端点从小到大排序,如果线段内没有测速点,选择这个右端点作为测速点。这样显然是最优的。
所以最后的时间复杂度就是 $O(n\log n)$
[CSP-S 2025] 社团招新
Description
给定 $n$ 个新成员和 $3$ 个社团,其中第 $i(1\le i\le n)$ 个新成员对社团 $j(1\le j\le 3)$ 的满意度为 $a_{i,j}$。总满意度为所有新成员对所有社团的满意度之和。在满足任意一个社团的成员数量不超过 $n/2$ 的前提下,最大化总满意度。
Solution
考虑贪心,对于每一个新成员,必然优先选择满意度最高的社团。但可能会出现某一社团选择的人数已经超过 $n/2$ 的情况。那么就需要转移一部分成员到其他社团。所以我们需要使得损失的贡献尽可能小,即最大减次大尽可能小。
那么我们只需要贪心选择满意度最高的社团,同时按照最大减次大的值排序即可。
Code
#include <bits/stdc++.h>
using namespace std;
#define MULTI_CASES
#define ll long long
#define int ll
#define endl '\n'
// #define NOI_IO
#define vi vector<int>
#define PII pair<int, pair<int, int>>
const int MaxN = 2e5 + 100;
const int INF = 1e9;
const int mod = 1e9 + 7;
int T = 1, N, M;
// int a[MaxN];
struct node
{
int x, y, z;
} a[MaxN];
inline void Solve()
{
vi g[10];
cin >> N;
int sum = 0;
int cnt1 = 0, cnt2 = 0, cnt3 = 0;
for (int i = 1; i <= N; i++)
{
cin >> a[i].x >> a[i].y >> a[i].z;
sum += max({a[i].x, a[i].y, a[i].z});
// if(a[i].x==max({}))
int x = max({a[i].x, a[i].y, a[i].z});
// cerr<<cnt<<" ";
if (x == a[i].x)
{
int cnt=min(a[i].x-a[i].y,a[i].x-a[i].z);
g[1].push_back(cnt);
cnt1++;
}
else if (x == a[i].y)
{
int cnt=min(a[i].y-a[i].x,a[i].y-a[i].z);
g[2].push_back(cnt);
cnt2++;
}
else if (x == a[i].z)
{
int cnt=min(a[i].z-a[i].x,a[i].z-a[i].y);
g[3].push_back(cnt);
cnt3++;
}
}
// cerr<<endl;
if (cnt1 <= N / 2 && cnt2 <= N / 2 && cnt3 <= N / 2)
{
cout << sum << endl;
return;
}
// cerr << cnt1 << " " << cnt2 << " " << cnt3 << endl;
int flag = 0;
if (cnt1 >= cnt2 && cnt1 >= cnt3)
flag = 1;
else if (cnt2 >= cnt1 && cnt2 >= cnt3)
flag = 2;
else if (cnt3 >= cnt1 && cnt3 >= cnt2)
flag = 3;
// cerr << flag << endl;
sort(g[flag].begin(), g[flag].end());
for (int i = 0; i < g[flag].size() - N / 2; i++)
{
// cerr<<g[flag][i]<<" ";
sum -= g[flag][i];
}
// cerr<<endl;
cout << sum << endl;
}
signed main()
{
#ifdef NOI_IO
freopen("club.in", "r", stdin);
freopen("club.out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(nullptr), cout.tie(nullptr);
#ifdef MULTI_CASES
cin >> T;
while (T--)
#endif
Solve();
return 0;
}
[CSP-S 2025] 道路修复
Solution
题目要求将原有的 $n$ 座城市两两连通的最小费用。那么显然需要使用最小生成树来求解。
注意到 $k\le 10$,那么我们可以通过枚举所有的 $2^k$ 种情况,分别求解一次最小生成树。复杂度为 $O(2^k(kn+m)\log(kn+m))$。期望得分 $32$ pts。
考虑优化,首先,我们可以先对原图跑一次最小生成树,可以求出 $N-1$ 条边,显然,剩余的边对于答案一定没用贡献,所以只需要保留这 $N-1$ 条边,并继续枚举 $2^k$ 种情况,每次只需要在这 $N-1$ 条边中选择 $k$ 条边即可。复杂度为 $O(2^kkn\log kn+m\log m)$。期望得分 $64$ pts。
考虑进一步优化,不难发现时间复杂度的瓶颈在排序上,所以我们可以在一开始就先预处理出 $N-1$ 条边和 $kn$ 条边,直接排序,在枚举过程中遇到没有选择的边,就直接跳过即可。复杂度为 $O(2^kkn+kn\log kn+m\log m)$。期望得分 $100$ pts。
Code
#include <bits/stdc++.h>
using namespace std;
// #define MULTI_CASES
#define ll long long
#define int ll
#define endl '\n'
#define vi vector<int>
#define PII pair<int, int>
const int MaxN = 1e6 + 100;
const int INF = 1e9;
const int mod = 1e9 + 7;
int T = 1, N, M, k;
int a[11][10000 + 5];
int town[11];
struct Edge
{
int u, v, w;
int from;
};
struct edge
{
int u, v, w;
bool used;
} e[MaxN];
struct DSU
{
vector<int> fa, sz;
void init(int n)
{
fa.resize(n + 1);
sz.assign(n + 1, 1);
iota(fa.begin(), fa.end(), 0);
}
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
bool join(int x, int y)
{
x = find(x);
y = find(y);
if (x == y)
return false;
if (sz[x] < sz[y])
swap(x, y);
fa[y] = x;
sz[x] += sz[y];
return true;
}
};
inline void Solve()
{
cin >> N >> M >> k;
for (int i = 1; i <= M; i++)
{
cin >> e[i].u >> e[i].v >> e[i].w;
e[i].used = false;
}
for (int i = 1; i <= k; i++)
{
cin >> town[i];
for (int j = 1; j <= N; j++)
cin >> a[i][j];
}
vector<int> idx(M);
iota(idx.begin(), idx.end(), 1);
sort(idx.begin(), idx.end(), [&](int i, int j)
{ return e[i].w < e[j].w; });
DSU dsu;
dsu.init(N);
vector<Edge> used;
used.reserve(N - 1);
int cnt_used = 0;
for (int id : idx)
{
if (dsu.join(e[id].u, e[id].v))
{
e[id].used = true;
cnt_used++;
used.push_back({e[id].u, e[id].v, e[id].w, 0});
if (cnt_used == N - 1)
break;
}
}
sort(used.begin(), used.end(), [](const Edge &A, const Edge &B)
{ return A.w < B.w; });
vector<vector<char>> keep(k + 1, vector<char>(N + 1, 0));
for (int t = 1; t <= k; ++t)
{
vector<Edge> tmp;
tmp.reserve(used.size() + N);
for (auto &be : used)
tmp.push_back(be);
for (int v = 1; v <= N; ++v)
tmp.push_back({N + 1, v, a[t][v], t});
sort(tmp.begin(), tmp.end(), [](const Edge &A, const Edge &B)
{ return A.w < B.w; });
DSU d;
d.init(N + 1);
int need = N + 1;
for (auto &ed : tmp)
{
if (d.join(ed.u, ed.v))
{
if (ed.u == N + 1)
keep[t][ed.v] = 1;
else if (ed.v == N + 1)
keep[t][ed.u] = 1;
if (--need == 1)
break;
}
}
}
vector<Edge> c;
c.reserve(used.size() + k * N);
for (auto &be : used)
c.push_back(be);
for (int t = 1; t <= k; ++t)
for (int v = 1; v <= N; ++v)
if (keep[t][v])
c.push_back({N + t, v, a[t][v], t});
sort(c.begin(), c.end(), [](const Edge &A, const Edge &B)
{ return A.w < B.w; });
ll ans = (ll)4e18;
int ALL = 1 << k;
for (int mask = 0; mask < ALL; ++mask)
{
int towns = __builtin_popcount((unsigned)mask);
DSU d;
d.init(N + k);
int pps = N + towns;
ll sum = 0;
if (towns)
{
for (int t = 1; t <= k; ++t)
if ((mask >> (t - 1)) & 1)
sum += town[t];
}
for (auto &ed : c)
{
if (ed.from == 0)
{
if (d.join(ed.u, ed.v))
{
sum += ed.w;
if (--pps == 1)
break;
}
}
else
{
int t = ed.from;
if (!((mask >> (t - 1)) & 1))
continue;
if (d.join(ed.u, ed.v))
{
sum += ed.w;
if (--pps == 1)
break;
}
}
}
if (pps == 1)
ans = min(ans, sum);
}
cout << ans << endl;
}
signed main()
{
#ifdef NOI_IO
freopen(".in", "r", stdin);
freopen(".out", "w", stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(nullptr), cout.tie(nullptr);
#ifdef MULTI_CASES
cin >> T;
while (T--)
#endif
Solve();
return 0;
} 本文采用 CC BY-NC 4.0 许可协议,转载请注明出处并保持非商业用途。