压缩dp之noip提高组2005 过河

题目链接: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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值