题目链接:https://vijos.org/p/1002
这题拿到手,很容易想到这样的一个dp方程:
f(i) = minof{f(i-j)+a[i]|j∈[s,t],且i-j>=0}
其中f(i)表示调到i位置最少需要的石子,a[i]表示i位置是否有石子,1表示有,0表示没有。
但是这题的L范围达到10^9,如果直接采用上面的dp方程只能拿到30分。
如何改进呢?我们很快注意到石子个数m最大不差过100,那么显然m个石子分布在很大的L上, 必然会存在相邻的石子距离非常大,那么f(i)会出现在较长的区间内值是一定的!
也就是说,这段很长的距离的f(i)值没必要继续一个个就算,因为这部分的f(i)和前面部分的f(i)是一样的,换句话说,其对答案没有贡献,只是起到一个“桥梁”、“传递”的作用,但是对我们来说,我们完全可以忽略这部分,直接跳到下一个石子!显然,如此一来,时间必然过得去。
那么忽略的部分是多少呢?换句话说,相邻石子距离压缩多少而不影响结果呢?当然我们希望越小越好。
那么我们只要知道多长距离以后,f(i)的值达到稳定。
上图中, x表示石子位置。其实从x+2T+1之后,f(i)开始稳定,所以我们最多只需要保留2*T的距离!
但是如果这样提交的话,我的程序只能拿到80分,有两个case过不去,发现都是s==t的case。
仔细想想,会发现,上面要想出现这种“传递"的作用,其实必须是局部可连续的,那么单s==t,就会导致所能跳达的点都是离散的,不符合前述的压缩做法。
所以加特判即可ac。详细代码如下:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cmath>
#include <vector>
#include <stack>
#include <queue>
#include <string>
#include <list>
#include <deque>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define EXP 1e-8
#define LL long long
int L,S,T,m;
int main(){
freopen("river.in","r",stdin);
freopen("river.out","w",stdout);
scanf("%d",&L);
scanf("%d%d%d",&S,&T,&m);
vector<int> a(m+20);
int x;
for (int i = 1; i <= m; ++i){
scanf("%d",&a[i]);
}
sort(a.begin()+1,a.begin()+m+1);
//printf("%d %d\n",a[0],a[1]);
if (S == T){
int ans = 0;
for (int i = 1; i <= m; ++i){
if (a[i] % S == 0){
++ans;
}
}
printf("%d\n",ans);
return 0;
}
vector<int> stone(25*m);
int j = 0;
for (int i = 1; i <= m; ++i){
if (a[i]-a[i-1] > 2*T){
j += 2*T;
stone[j] = 1;
}else{
j += a[i]-a[i-1];
stone[j] = 1;
}
}
vector<int> f(25*m);
f[0] = 0;
int k = j + T;
for (int i = 1; i <= k; ++i){
int maxs = INF;
for (int j = S; j <= T; ++j){
if (i-j>=0){
maxs = min(maxs,stone[i]+f[i-j]);
}
}
f[i] = maxs;
}
printf("%d\n",f[k]);
/*for (int i = 0; i <= L+T; ++i){
printf("%d %d\n",i,f[i]);
}*/
//system("pause");
return 0;
}