题解 P1052 【过河】
作者:互联网
在某人的挑唆之下来做了这道题,说是路径压缩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,我们还是按照常规思路,想一想有哪些地方是多余的,可以优化掉。
可以观察到题目中\(1\leq S \leq T \leq 10, 1\leq M\leq 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;
}
标签:过河,min,int,题解,石子,leq,P1052,last,mod 来源: https://www.cnblogs.com/lcezych/p/12114219.html