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

列不定方程 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 
#include 
#include 
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;
}


 

你可能感兴趣的:(C/C++,OJ解题报告)