题目大意 : 给你n个不同的身高,把他们拍成一列,按照高低高低高低、或者低高低高低高 排列 问你有多少种排列方法
Sample Input
4
1 1
2 3
3 4
4 20
Sample Output
1 l
2 4
3 10
4 740742376475050
思路:
我们考虑。前面已经有i-1个人现在要放第i个人的情况。由于i是最后放的所以他一定是最高的。无论他在哪个位置一定都是波峰。
所以在他前面的部分结尾一定为高低。在他后面的部分开始一定为低高。这样才能形成高低高低高。如果知道前面结尾高低的方法数n和后面开始低高的方法数m。那么在该位置的方法数就为n*m。而前面的个数和后面的个数是不确定的所以要枚举前面的个数j。而后面的个数就为i-j-1。由于状态转移要用到结尾高低的方法数和后面开始低高的方法数
所以我们用dp[i][0]表示有i个人且i个人结尾为高低的方法数。dp[i][1]表示有i个人且开始为低高的方法数。
那么有i个人排列的方法数就为ans[i]+=dp[j][0]*dp[i-j-1][1]*c[i-1][j]。c[i-1][j]为方法数。因为不确定前面j个人的标号。所以要在i-1个人中选j个人出来。开始一直纠结标号问题把自己都搞晕了。其实用第i个人把人分到两边。只这样只跟人数有关系系了。
但怎么更新状态转移需要用到的dp[i][1]和dp[i][0]呢?
把i个士兵排好后无非两种情况。开始为低高。开始为高低。那么排列的逆序也满足条件。也就是说结尾为高低的方法数和开始为低高的方法数相同。而对于人数一定的情况。开始为低高的人数和开始为高低的人数相等。
证明:我们用0表示波谷1表示波峰。
当n为偶数时.假设波峰开始的序列为1010.那么把它倒置一下就变成了0101了。也就是说每一个1打头的对应着一个0打头的。
当n为奇数时。假设波峰开始的序列为10101.假设第一个数大于最后一个数。那我们把序列最后一个放到第一个位置由于第一个数大于最后一个数所以序列就变成01010.如果第一个数小于最后一个数把第一个数放到最后就行了。所以
每一个1打头的对应着一个0打头的。
所以dp[i][0]=dp[i][1]=ans[i]/2。
AC 代码
#include <iostream>
#include <algorithm>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cstdio>
#include <deque>
#include <stack>
using namespace std;
typedef long long ll;
ll dp[22][2]={0},c[22][22]={0};
void f()
{
ll t,i,j;
c[1][1]=1;
c[1][0]=1;
for(i=2;i<=20;i++)
{
c[i][0]=c[i][i]=1;
for(j=1;j<i;j++)
c[i][j]=c[i-1][j]+c[i-1][j-1];
}
dp[1][0]=dp[1][1]=dp[0][0]=dp[0][1]=1;
for(i=2;i<=20;i++)
{
t=0;
for(j=0;j<i;j++)
t+=dp[j][0]*dp[i-j-1][1]*c[i-1][j];
dp[i][0]=dp[i][1]=t/2;
}
}
int main()
{
ll T,i,j,e,n,t;
f();
cin>>T;
while(T--)
{
cin>>e>>n;
if(n==1)
cout<<e<<" 1"<<endl;
else
cout<<e<<" "<<dp[n][0]*2<<endl;
}
return 0;
}