[SDOI2010]地精部落解题报告

这道题是我看了题解以后才做出来的,真是一道神题,但是写题解的大神都不愿解释得太详细,所以我想了很久才想明白。。

看了题解以后真的觉得很像数的划分、约瑟夫问题还有国王游戏,代码出奇地简洁,但是思维量相当地高。

主要思路:离散。

三个引理:

①在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);
}

你可能感兴趣的:(dp)