今天又开始做DP,今天这个题是一个组合递推DP,一开始在思考加入一个新的人后,会对前面的数列有什么影响,但是卡在了,这个新加入的人的高度,就直接看了题解。
才发现状态的划分是按照从小到大的顺序来进行的,这样也很符合状态的设计,因为递推性,当有n - 1个不同身高的人已经排好时,再加入一个更高的,其实也复合逻辑,并且之所以这样设计状态对于问题来说也是便于处理和正确的。
接着就是加入这个新的人后,之前的状态到底与新状态有什么关系。
先来考虑新的N加入的位置,因为条件要求的是,对于任意一个人来说,和它相邻的人必须比他高或者矮,这样的话因为新加入的这个人是这N个人中最高的,那这样的话,这个新加入的人只能插入在一个他的前面是 高低 的组合后面,同理考虑加入的这个新数后面的情况的话,就要是 低高 的情况,因为要保证后面的也满足条件.这个我陷入了一个思维的误区,就是片面的以为这个新的状态是通过它前一个状态,也就是 i - 1时的状态得来,但今天想明白了之后,明白递推不一定是仅仅由它前一个的状态得来,而是由 “前面” 出现过的 状态 得来.更多的是去发现,寻找这个当前状态与前面的那些状态有关,进而 “递推”.
言归正传,接着上面的讲,那样的话,这个问题就转化成了,当前状态是由前面的以高低结尾的状态 和 开头是以 低高的状态得来. 具体的思考一下.因为新加入的这个数改变了当前的最大值,所以,对于这个状态的设计可任选一些数 来 获得 “高低”关系.所以的话.
状态可以这样设计,
dp[i][0] 是 当序列长度为 i 时,以低高 结尾的方法数.
dp[i][1] 是 当序列长度为 i 时,以高低 结尾的方法数.
设 j 是新插入的位置,sum[i] 为 长度为 i 时的方法总数
sum[i] = dp[j - 1][0] * dp[i - j ][1] * c(j - 1,i - 1)
要乘组合数的原因是因为高低状态只与高度的大小有关,因为前面记录的状态都是 “较小数量”的状态,所以应该先从当前数量选取应选的数量.
然后枚举插入的位置就可以了,
最后还有一点就是,dp之间的递推关系,
这个的话,我是通过样例的找到的关系,就是dp[i][0] = dp[i][1] = sum[i] / 2;
其实也可以通过对称性来考虑.这个目前还没明白……….就先不写了.
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int MAXN = 25;
typedef long long LL;
LL sum[MAXN];
LL dp[MAXN][MAXN];
LL c(int m,int n)
{
LL a = 1;
LL b = 1;
for(int i = n;i >= n - m + 1;--i)
{
a *= i;
}
for(int i = 1;i <= m;++i)
{
b *= i;
}
return a / b;
}
void f1()
{
memset(sum,0,sizeof(sum));
dp[0][0] = dp[0][1] = 1;
dp[1][0] = dp[1][1] = 1;
sum[1] = 1;
for(int i = 2;i <= 21;++i)
{
for(int j = 1;j <= i;++j)
{
sum[i] += dp[j - 1][0] * dp[i - j][1] * c(j - 1,i - 1);
}
dp[i][0] = dp[i][1] = (sum[i] / 2);
}
return ;
}
int main()
{
f1();
//cout << f(2,3) << endl;
int T;
cin >> T;
while(T--)
{
int k,m;
cin >> k >> m;
cout << k << ' ' << sum[m] << endl;
}
return 0;
}
通过这个题 了解到了状态之间的转化,和状态的设计.