题目描述
从数轴原点 走到
,路上的
位置放置有限速牌,则
至
的位置,每走一个单位都需要
的时间。所以需要的总时间是
现在撤去至多 个限速牌,问所需的总时间最少为多少。
错误示范
昨天一看到这题第一反应就是个贪心暴力,把每次删牌子的贡献计算出来排序,然后拿总时间去减掉就是答案了。然后就是喜提 WA 5 了。后来仔细思考了一下便发现了暴力解法的错误点,因为暴力解法只考虑了最初始状态相邻路牌删掉以后的情况并未考虑几个区间连着删去,按照最前端那个最小的速度行驶完整个区间,是存在比考虑单独区间删去更加优秀的情况的。于是想到这里就发现可以用 dp 来解决这个问题了。
正确分析
我们把 的状态定义为 行驶至第
个路牌时,已经删去
个路牌的答案。
那么我们来看看状态转移方程
for(int i = 1;i <= n + 1;i ++)//枚举前 i 个路牌
for(int ls = 1;ls < i;ls ++)//上一个保留下来的路牌的位置
for(int j = 1;j <= n + 1;j ++)//已经保留了 j 个路牌
f[i][j] = min(f[i][j],f[ls][j - 1] + speed[ls] * (pos[i] - pos[ls]));
// 在前 ls 个标志中保留了 j - 1 个路牌 那么也就是说 在 ls 到 i 期间只保留了 ls 这一个路牌
// 换句话说就是从 ls 的这个路牌之后 一直到 i 路牌全都被 ban 掉了
// 那么对答案的贡献就是 ls 这个路牌的限速 一直维护到 第 i 个路牌截至 的时间贡献
//即 speed[ls] * (pos[i] - pos[ls])
那么搞定状态转移方程以后 最后的答案表示又该是什么呢
因为我们已经求出了删除数量不限制的所有情况 那么至多删除 个区间的答案
就是我们所有情况的子集 在取最小值就可以了
LL res = 1e18;
for(int i = n - k;i <= n + 1;i ++)
res = min(res,f[n + 1][i]);//取前 n + 1 个路牌 ban 掉 (0 ~ k)
// 也就是 n - k <= i <= n + 1 为啥说不是是 n - k + 1咧 因为最后一个路牌是在终点不能 ban
WA 5 code
#include<bits/stdc++.h>
using namespace std;
const int N = 500 + 100;
int pos[N];
int speed[N];
int sum[N];
void solve()
{
int n,l,k;//n 个路牌 长度为 l 可以删 k 个路牌
scanf("%d%d%d",&n,&l,&k);
pos[0] = 0;
pos[n + 1] = l;
for(int i = 1;i <= n;i ++) scanf("%d",&pos[i]);
for(int i = 0;i < n;i ++) scanf("%d",&speed[i]);
for(int i = 1;i <= n - 1;i ++)
{
sum[i] = ((speed[i - 1] * (pos[i + 1] - pos[i])) + (speed[i] * (pos[i + 2] - pos[i + 1])) - (speed[i - 1] * (pos[i + 2] - pos[i])));
}
sort(sum + 1,sum + n);
int res = 0;
for(int i = 1;i <= n;i ++)
{
//cout << speed[i - 1] * (pos[i + 1] - pos[i]) << endl;
res += speed[i - 1] * (pos[i + 1] - pos[i]);
}
/*
for(int i = n - 1;i >= 1;i --)
{
cout << sum[i] << endl;
}*/
int cnt = 0;
while(k --)
{
if(sum[n - 1 - cnt] > 0)
{
res -= sum[n - 1 - cnt];
cnt ++;
}
else break;
}
cout << res << endl;
}
int main()
{
int T = 1;
//cin >> T;
while(T --) solve();
//system("pause");
return 0;
}
AC Code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define N 600
LL pos[N],speed[N],f[N][N];
int main()
{
LL n,l,k;
scanf("%lld%lld%lld",&n,&l,&k);
pos[n + 1] = l;
for(int i = 1;i <= n;i ++) scanf("%lld",&pos[i]);
for(int i = 1;i <= n;i ++) scanf("%lld",&speed[i]);
for(int i = 0;i <= N - 90;i ++)
for(int j = 0;j <= N - 90;j ++)
f[i][j] = 1e18;
f[1][0] = 0;
for(int i = 1;i <= n + 1;i ++)//枚举前 i 个路牌
for(int ls = 1;ls < i;ls ++)//上一个保留下来的路牌的位置
for(int j = 1;j <= n + 1;j ++)//已经保留了 j 个路牌
f[i][j] = min(f[i][j],f[ls][j - 1] + speed[ls] * (pos[i] - pos[ls]));
// 在前 ls 个标志中保留了 j - 1 个路牌 那么也就是说 在 ls 到 i 期间只保留了 ls 这一个路牌
// 换句话说就是从 ls 的这个路牌之后 一直到 i 路牌全都被 ban 掉了
// 那么对答案的贡献就是 ls 这个路牌的限速 一直维护到 第 i 个路牌截至 的时间贡献
//即 speed[ls] * (pos[i] - pos[ls])
LL res = 1e18;
for(int i = n - k;i <= n + 1;i ++)
res = min(res,f[n + 1][i]);//取前 n + 1 个路牌 ban 掉 (0 ~ k)
// 也就是 n - k <= i <= n + 1 为啥说不是是 n - k + 1咧 因为最后一个路牌是在终点不能 ban
cout << res << endl;
return 0;
}