依旧三人场,另外两位和开了挂一样,前两题做巨快。
本人到 1h20min 才做完前两题,太有水平了知道吗。
两小时 291 分,致敬没调出来的 T3。
P15451 JOI 2026 SemiFinal 座位 3 / Seats 3 - 洛谷 (luogu.com.cn)
这时候早餐还没吃完,脑子转得慢,只想出来 log 解法。
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
LL a[N];
priority_queue<LL> Q;
int main () {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
n = 2 * n + 2;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
}
LL ans = 0;
for (int i = 1; i <= n; i ++) {
if (i & 1) {
Q.push(a[i]);
}
else {
ans = max(ans, a[i] + Q.top());
}
}
cout << ans << "\n";
return 0;
}
P15452 JOI 2026 SemiFinal 宝石商 / Jeweler - 洛谷 (luogu.com.cn)
橙做了一个多小时哈哈。我求我自己了能不能吃完早餐再上学。
想了一个小时才明白不是差分题,乱七八糟搞竟然过了。
赛后总结有两种做法,第一种是我乱搞的:


第二种是题解(更简单):

P15453 JOI 2026 SemiFinal 衣服 / Clothes - 洛谷 (luogu.com.cn)
赛时神笔做法,不道为哈 91 分,随便造一手数据都能卡过去的说。
正解二进制 bitset + dfs。用背包判断就慢很多。


cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 90;
LL a[N], b[N];
bitset<N> tp, bs;
int t;
int n;
bool jd() {
bs.reset();
bs[0] = 1;
for (int i = 1; i <= t; i ++) {
bs |= (bs << b[i]);
}
return ((bs & tp) == tp);
}
bool dfs(int last, int x) {
if (x > t) {
if (!jd()) {
return 0;
}
cout << "Yes\n" << t << "\n";
for (int i = 1; i <= t; i ++) {
cout << b[i] << " ";
}
cout << "\n";
return 1;
}
for (int i = last; i <= a[1]; i ++) {
b[x] = i;
if (dfs(i, x + 1)) {
return 1;
}
}
return 0;
}
void work() {
cin >> n;
for (int i = 1; i <= n; i ++) {
cin >> a[i];
a[i] = 23 - a[i];
}
if (a[n] < 0) {
cout << "No\n";
return ;
}
for (int i = 1; i <= n; i ++) {
tp[a[i]] = 1;
}
for (t = 0; t <= n; t ++) {
if (dfs(1, 1)) {
return ;
}
}
}
int main () {
ios::sync_with_stdio(false);
cin.tie(0);
work();
return 0;
}
P15454 JOI 2026 SemiFinal 顺流而下 / River Rafting - 洛谷 (luogu.com.cn)
很神笔的 dp。。。
cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 710;
int p[N]; LL c[N];
LL tmp[N][N];
vector<int> G[N];
int siz[N];
// f[i][j]表示当前节点已经经过 i 次
// 最远未照亮点距离当前节点为 j 的最小成本
vector<vector<LL>> dfs(int x) {
vector<vector<LL>> f;
for (int i = 0; i <= siz[x]; i ++) {
f.push_back(vector<LL> (siz[x] + 1, 1e18));
}
siz[x] = 1;
f[0][0] = 0;
for (int y : G[x]) {
auto g = dfs(y);
for (int j = 0; j <= siz[x]; j ++) {
// j 是经过 x 的次数
for (int k = 0; k <= siz[y]; k ++) {
// k 是经过 y 的次数
LL mnx = 1e18, mny = 1e18;
for (int i = 0; i <= siz[x] + siz[y]; i ++) {
// 枚举最远未照亮点与当前节点距离
if (i <= siz[x]) {
mnx = min(mnx, f[j][i]);
}
if (i <= siz[y]) {
mny = min(mny, g[k][i]);
}
tmp[j + k][i] = min(tmp[j + k][i], mnx + mny);
}
}
}
siz[x] += siz[y];
for (int i = 0; i <= siz[x]; i ++) {
for (int j = 0; j <= siz[x]; j ++) {
f[i][j] = tmp[i][j];
tmp[i][j] = 1e18;
}
}
}
// 深度提升操作:将距离从子节点到父节点转换
// 子节点距离为 d 的点,到父节点距离为 d + 1
for (int i = 0; i <= siz[x]; i ++) {
for (int j = siz[x]; j >= 1; j --) {
f[i][j] = f[i][j - 1];
}
f[i][0] = 1e18;
}
// 覆盖操作:在 x 节点进行顺流而下,覆盖未照亮的点
// i: 需要进行 i 次顺流而下(增加的经过次数)
// j: 当前最远未照亮点距离 u 为 j
for (int i = 0; i <= siz[x]; i ++) {
for (int j = 0; j <= siz[x]; j ++) {
// 如果最远距离 j 大于当前经过次数i,需要额外进行 (j - i)次漂流
// 每次漂流都在 x 子树内选择成本最小的城镇结束(c[x] 就是最小成本)
f[i][0] = min(f[i][0], f[j][i] + max(0, i - j) * c[x]);
}
}
return f;
}
int main () {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for (int i = 1; i < n; i ++) {
cin >> p[i];
G[p[i]].push_back(i + 1);
}
for (int i = 1; i <= n; i ++) {
cin >> c[i];
}
for (int i = 0; i <= n; i ++) {
for (int j = 0; j <= n; j ++) {
tmp[i][j] = 1e18;
}
}
// 自底向上预处理:计算每个节点的子树大小和子树内最小结束成本
// 注意:这里是从n到1倒序,因为题目保证Pi <= i
// 所以编号大的节点一定是编号小的节点的后代
for (int i = n; i >= 1; i --) {
siz[i] = 1;
for (int j : G[i]) {
siz[i] += siz[j];
c[i] = min(c[i], c[j]);
}
}
auto f = dfs(1);
LL ans = 1e18;
// 最终答案:根节点经过任意次数后,最远未照亮点距离为 0(即全部照亮)
for (int i = 0; i <= n; i ++) {
ans = min(ans, f[i][0]);
}
cout << ans << "\n";
return 0;
}