这道题是我看了题解以后才做出来的,真是一道神题,但是写题解的大神都不愿解释得太详细,所以我想了很久才想明白。。
看了题解以后真的觉得很像数的划分、约瑟夫问题还有国王游戏,代码出奇地简洁,但是思维量相当地高。
主要思路:离散。
三个引理:
①在n->n-1的转化过程中,我们删除了一个点后,我们可以将n-1个点视为仍是1~n-1的排列。
②在若排列Pn为一个合法抖动子序列,则交换i∈[1,n)与i+1,必能得到另一个抖动子序列。
③抖动序列的对称性,若存在第一段上升的长度为n的抖动子序列,则以n+1-x代x必能得到一个第一段下降的长度为n的抖动子序列。
设f[i][j]为长度为i的,以j开头的,第一段下降的抖动子序列的个数,则循题意可得2*f[n+1][n+1]即为答案。
考虑转移:
①若j的下一个是j-1,则需要一个长度为n-1的,以j-1开头的上升子序列;
再分两种情况:
若j==n,则j-1为i-1中正数第一个数,所以可以转移到f[i][1];
若j<n,则j-1为i-1中正数第i-1-(j-1)+1-1个数,所以可转移到f[i-1][j-i].
我们根据状态设定,显然有f[i][0]=f[i][1]=0.
∴f[i][j]->f[i-1][j-i].
②若j的下一个不是j-1,则对于任意一个以j-1开头的下降子序列,均可以通过交换j-1和j+1得到。
∴f[i][j]->f[i][j-1]
综,f[i][j]=f[i-1][j-i]+f[i][j-1].
减少代码量?一个技巧:
改变状态,令f[i][j]=f[i+1][j+1],则初始状态变更为f[1][1]=1,i∈[1,n],j∈[1,n].
#include<iostream> using namespace std; #include<cstdio> #include<cmath> #include<algorithm> #include<cstring> int f[2][10001]; int main(){ int n,p; bool tmp; scanf("%d%d",&n,&p); f[1][1]=1; for(int i=2;i<=n;++i){ tmp=i&1; for(int j=1;j<=i;++j) f[tmp][j]=(f[!tmp][i-j+1]+f[tmp][j-1])%p; } printf("%d",(f[tmp][n]<<1)%p); }