【BZOJ1925】[SDOI2010]地精部落(动态规划)
【BZOJ1925】[SDOI2010]地精部落(动态规划)
题面
题解
一道性质\(dp\)题。(所以当然是照搬学长PPT了啊
先来罗列性质,我们称题目所求的序列为抖动序列:
- 一个抖动序列的连续子序列还是一个抖动序列。
- 如果在一个抖动序列中\(x\)与\(x+1\)不相邻,那么交换两者的位置这个序列仍是抖动序列。
- 如果将一个抖动序列中所有大于\(x\)的元素全部\(+1\),那么这个序列仍然是抖动序列。
- 一个\([1,x]\)的抖动序列可以映射到一个\([y-x+1,y]\)的抖动序列。
上面的性质都比较显然。证明什么的感性理解一下就好了。
现在来写\(dp\)。
设\(f[i][j]\)表示\([1,i]\)构成的排列中,第一个数为\(j\)的抖动序列的个数,并且强制第一个数大于第二个数,即第一个位置上是一个峰。
第一个数这已经确定,现在考虑第二个位置上的数。
如果这个数不是\(j-1\),那么我们必定可以交换\(j\)以及\(j-1\),不难发现这样子是一一对应的,也就是\(f[i][j]\leftarrow f[i][j-1]\)
否则这个数是\(j-1\)。那么我们先把第一个\(j\)个忽视掉,剩下的部分的值是\([1,j-1]\cup[j+1,i]\),那么我们这样子想,我们只需要剩下的这些值离散,变成了\([1,i-1]\),然后构成了一个以\(j-1\)开头的抖动序列,再把\([j+1,i-1]\)这一段全部加一,最后再把\(j\)放回到开头,这样子就可以得到一个抖动序列了。但是我们似乎没法直接从\(f[i-1][j-1]\)转移过来,因为在上述状态中\(j-1\)是一个峰,但是在\(f[i][j]\)的状态中,\(j-1\)是一个谷。然而并没有什么关系,我们用上面的最后一个结论,把所有的数全部映射一下,即\(j-1\)映射到\(i-j+1\)。那么就可以得到\(f[i][j]\leftarrow f[i-1][i-j+1]\)
那么这样子就可以\(dp\)了,转移方程是\(f[i][j]=f[i][j-1]+f[i-1][i-j+1]\)
那么答案就是\(\sum f[n][i]\)啦,但是别忘了这样子强制了第一个位置是峰,再映射一下,发现还要乘个二就可以把谷的贡献给算进来啦。
#include<iostream>
#include<cstdio>
using namespace std;
int n,MOD,f[2][4250],ans;
int main()
{
scanf("%d%d",&n,&MOD);f[0][1]=1;
for(int i=1,nw=1,pw=0;i<=n;++i,nw^=1,pw^=1)
for(int j=1;j<=i;++j)
f[nw][j]=(f[nw][j-1]+f[pw][i-j+1])%MOD;
for(int i=1;i<=n;++i)ans=(ans+f[n&1][i])%MOD;
ans=(ans+ans)%MOD;printf("%d\n",ans);
return 0;
}