在某人的挑唆之下来做了这道题,说是路径压缩dp,然鹅我做完以后并没有发现什么和路径压缩有关的东西。
题目的意思大概就是给你一条数轴和一些石子,再给你每一次可以往前走的长度区间,求想要走完这条数轴最少需要经过多少个石子。
首先对于30%的数据,(L leq 10000),大概就是一个线性dp。
不难想到对于每一个点(i),都可以由([i-s,i-t])转移过来。
设(f[i])表示在点(i)时的最小答案,那么我们就有了一个最基本的转移方程:(f[i]=min(f[k]))。
当然如果(i)处有石子,(f[i]++)。
这样是从后往前推的,也可以从前往后。
大概就是这样:
for(int i = 0; i < L; i ++)
{
if(a[i] == 1) f[i] += 1;
for(int j = s; j <= t; j ++)
{
if(i + j >= l) ans = min(ans, f[i]);
f[i + j] = min(f[i], f[i + j]);
}
}
100%的数据:(L leq 10^9)
显然这种情况如果直接开(L)的数组会MLE,我们还是按照常规思路,想一想有哪些地方是多余的,可以优化掉。
可以观察到题目中(1leq S leq T leq 10, 1leq Mleq 100)
也就是说每一次走不超过十步,一共不超过100个石子。
显然这种情况下两个石子之间的距离会非常的长,并且要走的步数也非常的多。而这些步数中绝大多数都是没有用的,那么一个自然的想法就是把两个石子之间的距离缩短(难道这就是传说中的路径压缩?)
先从最简单的想起,假如一次只能走(S)或者(T)步,从原点开始走,那么显然一定可以走到(lcm(S,T))这个位置。在这种情况下,如果((0, lcm(S,T)])之间没有石子,那么((0,lcm(S,T)])就是无用的,因为它不会对答案产生任何影响,可以把这一段删去。
推广一下就可以知道,从点(i)开始,一次走([S,T])步,一定可以到达(i+lcm(S...T))这个点
再利用上面的结论,就可以对两点之间的距离进行缩短
核心代码:
for(int i = 1; i <= n; i ++)
{
if(b[i] - last >= mod) b[i] = last + (b[i] - last) % mod;
a[b[i]] = 1;
last = b[i];
}
完整代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int L = 5000005;
int n, s, t, l, b[L], a[L], f[L], mod;
int main()
{
scanf("%d", &l);
scanf("%d%d%d", &s, &t, &n);
mod = 10;//这里是为了增加间隔
for(int i = s; i <= t; i ++) mod *= i;
memset(f, 0x3f, sizeof(f));
int ans = 0x3f3f3f3f, last = 0; f[0] = 0;
for(int i = 1; i <= n; i ++) scanf("%d", &b[i]);
sort(b + 1, b + 1 + n);
for(int i = 1; i <= n; i ++)
{
if(b[i] - last >= mod) b[i] = last + (b[i] - last) % mod;
a[b[i]] = 1;
last = b[i];
}
if(l - last >= mod) l = last + (l - last) % mod;
for(int i = 0; i < l; i ++)
{
if(a[i] == 1) f[i] += 1;
for(int j = s; j <= t; j ++)
{
if(i + j >= l) ans = min(ans, f[i]);
f[i + j] = min(f[i], f[i + j]);
}
}
for(int i = 1; i <= n; i ++) cout<<b[i] << endl;
cout << ans;
}