题解:P10432 [JOIST 2024] 滑雪 2 / Ski 2
神秘性质题,挺不错的。
性质
1 对于同一个点,我们不能同时对其提升海拔与建立接口
考虑设
若
若
性质
2 存在一种最优策略,使得每一个高度
i 上都至少有一个点不被提高,且该点为原本海拔为i 的所有点中c 值最小的
首先我们显然不能把该高度的所有节点都向上提。
其次,我们如果要在当前高度进行选点,一定是选择当前高度下
最后,如果我们选的点不是原本就在该高度下的点,那么显然有这个点的
综上,性质得证。
性质
3 每一个高度都会尽量用完从上一个高度提升海拔过来的所有点
考虑这部分点已经被提升过海拔,于是由 性质
于是可以设计状态
下将高度
考虑初始化:显然有
考虑转移:
转移有两种方式:
- 通过 ”提升海拔“ 转移。设高度为
i+1 的节点共有x 个,则有dp_{i,j,k} + K \cdot \max(x+k-j , 0) \to dp_{i+1,j,\max(x+k-j , 0)} 。这个式子含义比较显然。我们考虑第i+1 层的点会有多少不能向前面连边,显然这样的点共有\max(x+k-j) 个,于是将其向上移动即可。并且我们有:对于每一个被占用的接口,它都是由一个免费接口还没有被占用的点占用的,故我们总的可用接口数量j 不变; - 通过 ”建立接口“ 转移。易得
dp_{i,j,k} + \min c_i \to dp_{i,j+1,k} ,其中\min c_i 为原本海拔不超过i 的点中c 值的最小值。
显然
如果高度
显然在离散化后,高度最多只剩下
代码:
#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair <int , int>
#define pb push_back
#define fi first
#define se second
#define fastio ios::sync_with_stdio(false) , cin.tie(0) , cout.tie(0);
using namespace std;
const int MAXN = 330 , INF = 0x3f3f3f3f;
const long long LINF = 0x3f3f3f3f3f3f3f3fLL;
struct node
{
int h;
ll c;
};
int n;
int tmpk[MAXN] , cnt[MAXN];
ll K , cval[MAXN] , dp[MAXN][MAXN][MAXN];
node a[MAXN];
int main()
{
fastio;
cin >> n >> K;
for(int i = 1 ; i <= n ; i++)
{
cin >> a[i].h >> a[i].c;
tmpk[i] = a[i].h;
}
sort(tmpk + 1 , tmpk + 1 + n);
memset(cval , INF , sizeof(cval));
memset(dp , INF , sizeof(dp));
tmpk[0] = -INF;
for(int i = 1 ; i <= n ; i++) tmpk[i] = max(tmpk[i] , tmpk[i - 1] + 1);
for(int i = 1 ; i <= n ; i++)
{
a[i].h = lower_bound(tmpk + 1 , tmpk + 1 + n , a[i].h) - tmpk;
cnt[a[i].h]++;
cval[a[i].h] = min(cval[a[i].h] , a[i].c);
}
for(int i = 1 ; i <= n ; i++) cval[i] = min(cval[i] , cval[i - 1]);
dp[0][1][0] = 0;
for(int i = 0 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
for(int k = 0 ; k <= n ; k++)
{
if(dp[i][j][k] == LINF) continue;
dp[i + 1][j][max(0 , cnt[i + 1] + k - j)] = min(dp[i + 1][j][max(0 , cnt[i + 1] + k - j)] , dp[i][j][k] + K * max(0 , cnt[i + 1] + k - j));
dp[i][j + 1][k] = min(dp[i][j + 1][k] , dp[i][j][k] + cval[i]);
// cout << i << ' ' << j << ' ' << k << ' ' << dp[i][j][k] << endl;
}
}
}
long long ans = LINF;
for(int i = 1 ; i <= n ; i++) ans = min(ans , dp[n][i][0]);
cout << ans;
return 0;
};