Tyvj P1059 过河问题 - DP&状态压缩

时间: 1000ms / 空间: 131072KiB / Java类名: Main

背景

NOIP2005 提高组 第二道

描述

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

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

  对于30%的数据,L <= 10000; 
  对于全部的数据,L <= 10^9。

输入格式

  输入的第一行有一个正整数L(1 <= L <= 10^9),表示独木桥的长度。第二行有三个正整数S,T,M,分别表示青蛙一次跳跃的最小距离,最大距离,及桥上石子的个数,其中1 <= S <= T <= 10,1 <= M <= 100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置(数据保证桥的起点和终点处没有石子)。所有相邻的整数之间用一个空格隔开。 

输出格式

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

测试样例1

输入

10
2 3 5
2 3 5 6 7

输出

              2


首先很容易想到是动态规划,dp[n]表示达到第n个点最少需要踩的石头数量。dp[i]=min { dp[i-j] }, j∈[S,T]. 如果这个点有石头就再加上一。

但是L可能给得很大(最大10e9),因此直接开dp数组的话内存会不够用,经过观察发现M其实很小,所以进行压缩。
压缩的根据如下,因为S到T之间都能取,当S<T时必然出现S和S+1,显然它们是互素的。

列不定方程 x*S+y*(S+1)=Q,Q>=s*(s+1),取特解x=x'+(S+1)*t, y=y'-S*t

会发现x和y总有非负整数解,每次Q加1就相当于少取一个S多取一个(S+1),遇到Q的整数倍表现出周期性。观察到在这种情况下S不大于9,T不大于10。

这样,对于某块石头,如果它后面紧跟着那块石头与它相距超过90,我们可以将从后一块石头开始的所有石头往前移动直到最近的那块与它的距离是90而不改变规划结果。

这样可以将装石头的长度压缩到90*100=9000,可以开数组了,然后套用最初的dp就行了。

特例是S==T的情况,判断每块石头的位置是否为S的整数倍就行。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int L;
int S, T, M;
int dp[10000];
int stone[10000] = {0};
int stonepoint[100];

int main()
{
	scanf("%d",&L);
	scanf("%d %d %d",&S,&T,&M);
	for (int i = 0; i < M; i++)
		scanf("%d",stonepoint+i);

	if (S != T)
	{
		sort(stonepoint, stonepoint + M);

		if (stonepoint[0] > 90)
		{
			int gap = stonepoint[0] - 90;
			stonepoint[0] = 90;
			for (int i = 1; i < M; i++)
				stonepoint[i] -= gap;
		}

		for (int i = 1; i < M; i++)
		{
			if (stonepoint[i] - stonepoint[i - 1] > 90)
			{
				int gap = stonepoint[i] - stonepoint[i - 1] - 90;
				stonepoint[i] = stonepoint[i - 1] + 90;
				for (int j = i + 1; j < M; j++)
					stonepoint[j] -= gap;
			}
		}

		if (L > stonepoint[M - 1] + 90)
			L = stonepoint[M - 1] + 90;

		for (int i = 0; i < M; i++)
			stone[stonepoint[i]] = 1;

		dp[0] = 0;
		for (int i = 1; i < S; i++)
			dp[i] = 0x3f3f3f3f;
		for (int i = S; i < T + 1; i++)
			dp[i] = stone[i];	
		for (int i = T + 1; i <= L; i++)
		{
			int min = 0x3f3f3f3f;
			for (int j = S; j <= T; j++)
				min = min < dp[i - j] ? min : dp[i - j];
			dp[i] = min+stone[i];
		}
		printf("%d\n",dp[L]);
	}
	else
	{
		int count = 0;
		for (int i = 0; i < M; i++)
			if (!(stonepoint[i] % S)) count++;
		printf("%d\n",count);
	}

	return 0;
}


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值