bzoj1925 [Sdoi2010]地精部落(dp)

来源:互联网 发布:linux 进程cpu使用率 编辑:程序博客网 时间:2024/05/16 18:57

程序5分钟,思维两小时hh。
问题就是求长度为n的排列满足抖动序列的个数。我们首先要发现答案是具有对称性的,即如果存在一个排列满足答案且第一个数为山峰,则一定还存在一个排列满足答案且第一个数为山谷(把原排列中的每个数x都换成n+1-x即可),推广一下:如果存在一个长度为i的序列满足要求且第一个数为山峰的话,则一定还存在一个长度为i的序列满足要求且第一个数为山谷(把这i个数从小到大排列,分别为a[1]…a[i],则把每个a[x]都换成a[i+1-x]即可),也就是说
长度为i的序列以第j大的数开头为山峰的个数=长度为i的序列以第i+1-j大的数开头为山谷的个数。设f[i][j]表示长度为i的序列以第j大的数开头为山峰的个数,g[i][j]表示长度为i的序列以第j大的数开头为山谷的个数。则有
f[i][j]=g[i][i+1-j]。我们显然还有f[i][j]=k=1j1g[i1][k]
综合两式可以得到f[i][j]=k=ij+1i1f[i1][k],决策时O(n)的,而状态是O(n2)的,拿不到满分,我们发现f[i][j]只比f[i][j-1]多了一项f[i-1][i-j+1]!因此我们的状态转移方程可以优化为:f[i][j]=f[i][j-1]+f[i-1][i-j+1]。O(n2)可以过了。初值为f[1][1]=1.注意到这道题内存只有64MB,还要使用滚动数组。
tips:f[i][j]的j是把i个数从小到大排列后的角标j,即第j大的数。
滚动数组注意清0的问题。。。

#include <cstdio>#include <cstring>#define N 4205int n,mod,p=0,f[2][N];//f[i][j]表示i个数,第一个数为第j大的且为谷峰 int main(){//  freopen("a.in","r",stdin);    scanf("%d%d",&n,&mod);    f[0][1]=1;    for(int i=2;i<=n;++i){        if(i==3) f[0][1]=0;        for(int j=2;j<=i;++j)            f[p^1][j]=(f[p^1][j-1]+f[p][i-j+1])%mod;        p^=1;    }    int ans=0;    for(int i=2;i<=n;++i) ans=(ans+f[p][i])%mod;    if(n==1) puts("1");    else printf("%d\n",ans*2%mod);    return 0;}
原创粉丝点击