[dp] P10432 [JOISC 2024] 滑雪 2 (Day1)
posted on 2025-04-21 07:49:37 | under | source
题意:\(n\) 个点,高度为 \(h_i\),可以花费 \(K\) 让 \(h_i\gets h_i+1\)。起初每个点有一个装置,花费 \(c_i\) 增置一个(任意次)。若 \(h_i>h_j\) 且 \(j\) 有装置,则可以花费一个并连接 \(i\to j\)。求最小花费,使得存在一个点可以被所有点到达。\(n\le 3\times 10^2\)。
相当于连成内向树。
考虑按高度逐个扫描,记 \(f_{i,j,k}\) 为考虑 \(\le i\) 的高度,有 \(j\) 个闲置接口,\(k\) 个点现在高 \(i\) 且准备抬到 \(i+1\)(这些点尚未加入树),的最小花费。初始化就是 \(f_{0,1,0}=0\) 表示可以选一个根。
考虑 \(i\) 向 \(i+1\) 转移。首先,需加入 \(i+1\) 高度上的原有的点,同时将 \(k\) 个点花费 \(k\times K\) 的代价抬高。
然后注意到一个性质:\(j\) 个闲置接口一定尽量用完。因为用一个后它自己也会产生一个,所以 \(j\) 不变。相当于白嫖。而且早晚都要连,延迟连边没有任何好处还带来额外代价。
最后考虑新增接口,形如完全背包,那么现在的问题是新增一个接口的花费?发现每一轮我们至少将 \(1\) 个点连入树上,那么我肯定选择连接新增的点中 \(c\) 最小的。对于原来抬上来的点,就算它的 \(c\) 可能更小我们也无需考虑,因为在它的那一轮有更小的 \(c\) 已经被计算了。综上代价即为 \(\min\limits_{h_p\le i+1}c_p\)。
转移很朴素可以看代码。
现在是 \(O(n^2H)\) 的。考虑离散化高度,因为每轮至少连入一个点,所以有用的高度只有将所有点从原先高度上向右依次排开的高度。变成 \(O(n^3)\)。
实现时注意转移代价可能超出 long long,特判一下。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define i128 __int128
#define MIN(a, b) a = min(a, b)
const int N = 3e2 + 5, inf = 1e12;
const i128 qwq = 1;
int n, K, h[N], c[N], m, idw[N], f[N][N][N];
map<int, bool> mp;
map<int, int> ct, mc;
signed main(){
cin >> n >> K;
for(int i = 1; i <= n; ++i){
scanf("%lld%lld", &h[i], &c[i]);
int hh = h[i];
++ct[hh];
while(mp.count(hh)) ++hh;
mp[hh] = true, idw[++m] = hh, mc[hh] = inf;
}
for(int i = 1; i <= n; ++i) MIN(mc[h[i]], c[i]);
sort(idw + 1, idw + 1 + m);
memset(f, 0x3f, sizeof f); f[0][1][0] = 0;
for(int i = 1, mi = inf; i <= m; ++i){
int cnt = ct[idw[i]];
for(int j = 1; j <= n; ++j)
for(int k = 0; k <= n - cnt; ++k){
if(qwq * (idw[i] - idw[i - 1]) * k * K > inf) continue;
MIN(f[i][j][k + cnt - min(j, k + cnt)], f[i - 1][j][k] + (idw[i] - idw[i - 1]) * k * K);
}
mi = min(mi, mc[idw[i]]);
for(int j = 1; j < n; ++j)
for(int k = 0; k <= n; ++k)
MIN(f[i][j + 1][k], f[i][j][k] + mi);
}
int ans = inf;
for(int i = 1; i <= n; ++i) MIN(ans, f[m][i][0]);
cout << ans;
return 0;
}

浙公网安备 33010602011771号