HDU4055 Number String --极经典的计数dp!

先简单说一下题意:给定一个只包含‘I’、‘D’、‘?’的字符串,其长度记为len,求解满足这种由字符串规定变化规律的(len+1)的排列个数,‘I’(increasing)表示前一个数字比后一个数字大,‘D’(decreasing)表示前一个数字比后一个数字小,‘?’表示前一个数字与后一个数字的大小关系任意。比如:满足“DIIDID”变化规律的一个排列是 3 1 2 7 4 6 5,即减小、增大、增大、减小、增大、减小的变换关系,题目链接:点击打开链接

这里还有一个官方的hint:

Permutation {1,2,3} has signature "II".Permutations {1,3,2} and {2,3,1} have signature "ID".Permutations {3,1,2} and {2,1,3} have signature "DI".Permutation {3,2,1} has signature "DD"."?D" can be either "ID" or "DD"."??" gives all possible permutations of length 3.

HDU4055 乍一看不像DP问题,很明显,确实不具有最优子结构,你想啊,容易想到把状态定义为F(i)(1<=i<=n),表示i的符合条件的排列,但是i的具体的排列对后面明显有影响呢?

而后数十分钟的苦苦思索,却不得其所,为此求解各大博主,并测试了好几个博主的效率和方法,弄了我这个比较好懂且效率还比较可以的算法,题目要求java10s以内,其他语言5s以内,本算法大约1.7s,测试过几个3s和4s的,并且感觉写得太复杂。

下面介绍计数dp经典算法:

状态一开始就没有找对,怎么能那么直接的定义状态呢,一般是很难直接求解的,在这里定义dp[i][j]为状态,表示i的满足变换规律的排列,并且以数字j为结尾。

边界条件很简单:dp[1][1] = 1,但是注意每个测试样例必须要全部初始化为0,因为在计算过程中会需要一些并没有计算的值,这些值为0,正好避免了设置条件判断,在后面的代码中有提示的地方。

状态转移方程为:(sum表示求和)

简单理解去掉后效性:处理dp[1~i][]的过程中i是依次1~n相加。处理完dp[i-1][]后,加入的数即为i,而dp[i][j]是要将i放进去j换 出来,而这里有一种将i放进去j换出来,同时不影响升降顺序的方法是:将dp[i-1][j]的i-1个数的序列中 ≥j 的数都加1,这样i-1变成了i,j变成了j+1,而j自然就补在后面了。 --来自某位机智的博主(点击打开链接)(感谢提点)

1.‘I’:dp[i][j] = sum(dp[i-1][x])(1<=x<=j-1)  这个过程可以化简为:dp[i][j] = dp[i][j-1] + dp[i-1][j-1];

2.‘D’:dp[i][j] = sum(dp[i-1][x])(j<=x<=i-1) 可化简为:dp[i][j] = dp[i][j+1] +dp[i-1][j];

3.‘?’:dp[i][j] = sum(dp[i-1][x])(1<=x<=i-1)

这个过程还是比较好理解的,只要搞懂前面的怎么让这个过程变成无后效性。

具体代码如下:

#include
#include
using namespace std;
const int m = 1e9 + 7;
const int n = 1010;
int dp[n][n];
string s;
int main()
{
	while (cin >> s)
	{
		int len = s.length()+1;//排列的最大数比字符个数大1
		memset(dp, 0, sizeof(dp));
		dp[1][1] = 1;
		for (int i = 2; i <= len; ++i)
		{
			char jud = s[i - 2];
			if (jud == 'I')
			{
				for (int j = 2; j <= i; ++j)//没有1结尾的递增,当然也可以写进去
				{
					dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - 1]) % m;
				}
			}
			else if (jud == 'D')
			{
				for (int j = i-1; j >=1; --j)
				{
					dp[i][j] = (dp[i][j + 1] + dp[i - 1][j]) % m;
				}
			}
			else
			{
				int sum = 0;
				for (int j = 1; j < i; ++j)
				{
					sum = (sum + dp[i - 1][j]) % m;
				}
				for (int j = 1; j <= i; ++j)
					dp[i][j] = sum;
			}
		}
		int sum = 0;
		//计算所有符合条件的排列
		for (int j = 1; j <= len; ++j)
			sum = (sum + dp[len][j]) % m;
		printf("%d\n", sum);
	}
	return 0;
}

你可能感兴趣的:(ACM训练)