DP起手练习8(路径压缩思想)

题目描述

P1052 过河【NOIP2005提高T2】

在河上有一座独木桥,一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子,青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数,我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点: 0 , 1 , … , L {0,1,…,L} 0,1,,L(其中 L {L} L是桥的长度)。坐标为0的点表示桥的起点,坐标为 L {L} L的点表示桥的终点。青蛙从桥的起点开始,不停的向终点方向跳跃。一次跳跃的距离是 S 到 T {S到T} ST之间的任意正整数(包括 S , T {S,T} S,T)。当青蛙跳到或跳过坐标为 L {L} L的点时,就算青蛙已经跳出了独木桥。

题目给出独木桥的长度 L {L} L,青蛙跳跃的距离范围 S , T , {S,T,} S,T,桥上石子的位置。你的任务是确定青蛙要想过河,最少需要踩到的石子数。

输入格式

第一行有1个正整数 L ( 1 ≤ L ≤ 1 0 9 ) {L(1 \le L \le 10^9)} L(1L109),表示独木桥的长度。

第二行有3个正整数 S , T , M , {S,T,M,} S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离及桥上石子的个数,其中 1 ≤ S ≤ T ≤ 10 , 1 ≤ M ≤ 100 {1 \le S \le T \le 10,1 \le M \le 100} 1ST10,1M100

第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子).所有相邻的整数之间用一个空格隔开。

输出格式

一个整数,表示青蛙过河最少需要踩到的石子数。

输入输出样例
输入 #1
10
2 3 5
2 3 5 6 7
输出 #1
2
说明/提示
对于30%的数据, L ≤ 10000 {L \le 10000} L10000;
对于全部的数据, L ≤ 1 0 9 {L \le 10^9} L109.

思路

第一眼看到这道题,就知道是DP,但为了保险起见,还是先爆搜吧…
(方法:枚举走到或超过终点所有的方式,并计算统计最小值)

#include
using namespace std;

int L,s,t,m,ans=1e9+7;
int a[1002];
bitset<1000000005>Map;//会MLE!

inline void Dfs(int k,int sum)
{
	if(k>=L)
	{
		if(sum<=ans)ans=sum;
		return;
	}
	if(sum>=ans)return;//虽然快点,但没什么用...
	for(int i=t;i>=s;i--)
	{
		if(Map[k+i])Dfs(k+i,sum+1);
		else Dfs(k+i,sum);
	}
	return;
}

int main()
{
	scanf("%d",&L);
	scanf("%d%d%d",&s,&t,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d",&a[i]);
		Map[a[i]]=1;
	}
	Dfs(0,0);
	printf("%d\n",ans);
	return 0;
}

然后30分到手 只有10分QAQ…(TLE+MLE)
我们来想想为什么,观察数据,无非就是S,T太小了,有什么办法呢?
因为S,T均不大于10,所以我们就得用路径压缩处理问题了.由于我们关心的不是石子的距离远近,而是能不能踩到这个石头,所以距离为18000000和距离为90就是等效的,因为我们都有办法走若干步使得距离达到0,而当距离s<(step-1)*step,这种做法就不一定成立.所以这题的最小可取距离为 9 ∗ 10 = 90 {9*10=90} 910=90,我们将比90大的距离都赋为90,然后再搜索,这样能极大地提高效率.(但 s = t {s=t} s=t时需要特判,但很简单,具体看代码.)

#include
using namespace std;

int L,s,t,m,ans=1e9+7;
int a[1002],lj;
int f[100005],jin[100005];
int tag[100005];

inline void Dfs(int k,int sum)
{
	if(k>=lj)
	{
		if(sum<=ans)ans=sum;
		return;
	}
	if(sum>=ans)return;
	for(int i=t;i>=s;i--)
	{
		if(tag[k+i])Dfs(k+i,sum+1);
		else Dfs(k+i,sum);
	}
	return;
}

int main()
{
	scanf("%d",&L);
	scanf("%d%d%d",&s,&t,&m);
	for(int i=1;i<=m;i++)
	scanf("%d",&a[i]);
	if(s==t)//特判(如果石子位置是每次能跳距离的倍数,则答案加1)
	{
		ans=0;
		for(int i=1;i<=m;i++)
		if(a[i]%s==0)ans++;
		printf("%d\n",ans);
		return 0;
	}
	sort(a+1,a+m+1);
	a[0]=0;
	jin[m+1]=min(L-a[m],100);
	for(int i=1;i<=m;i++)
	{
		jin[i]=min(a[i]-a[i-1],90);
		lj+=jin[i];
		tag[lj]=1;
	}
    lj+=jin[m+1];//上面几排为路径压缩
	Dfs(0,0);
	printf("%d\n",ans);
	return 0;
}

然而还是只有30分QAQ(TLE),看来我们只有用DP解决问题了.
我们很容易想到用f[i]表示走到i位置最小踩石子数.然后我们知道走到这个位置的最小踩石子数一定是走到前一个位置的最小踩石子数再加上i位置的标记(有石子则为1),所以状态转移方程:
( f [ 0 ] = 0 {f[0]=0} f[0]=0) f [ i ] = m i n ( f [ i − j ] + t a g [ i ] , f [ i ] ) ( s = < j < = t ) {f[i]=min(f[i-j]+tag[i],f[i])(s=f[i]=min(f[ij]+tag[i],f[i])(s=<j<=t)
最后答案 a n s = m i n ( f [ l j − > l j + 9 ] ) {ans=min(f[lj->lj+9])} ans=min(f[lj>lj+9]).( f [ i ] {f[i]} f[i]要赋初值(1e9))!

#include
using namespace std;

int L,s,t,m,ans=1e9+7;
int a[1002];
int f[10005],jin[10005];
int tag[10005];

int main()
{
	scanf("%d",&L);
	scanf("%d%d%d",&s,&t,&m);
	for(int i=1;i<=m;i++)
	scanf("%d",&a[i]);
	if(s==t)
	{
		ans=0;
		for(int i=1;i<=m;i++)
		if(a[i]%s==0)ans++;
		printf("%d\n",ans);
		return 0;
	}
	sort(a+1,a+m+1);
	a[0]=f[0]=0;//初始必须为零!
	jin[m+1]=min(L-a[m],100);
    int lj=0;
	for(int i=1;i<=m;i++)
	{
		jin[i]=min(a[i]-a[i-1],90);
		lj+=jin[i];
		tag[lj]=1;
	}
    lj+=jin[m+1];
	for(int i=1;i<=lj+9;i++)//DP核心
	{
		f[i]=1e9+7;//赋大值即可
		for(int j=s;j<=t;j++)
		if(i>=j)f[i]=min(f[i],f[i-j]+tag[i]);
	}
	for(int i=lj;i<=lj+9;i++)
	ans=min(ans,f[i]);//统计最优解
	printf("%d\n",ans);
	return 0;
}

然后就可以A了qwq!

你可能感兴趣的:(NOIP,————DP————,线性DP)