【记录】[JOI 2026 SemiFinal] 前四题

依旧三人场,另外两位和开了挂一样,前两题做巨快。

本人到 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;
}