[SDOI2010][BZOJ 1925]地精部落

Description

传说很久以前,大地上居住着一种神秘的生物:地精。 地精喜欢住在连绵不绝的山脉中。具体地说,一座长度为 N 的山脉 H可分 为从左到右的 N 段,每段有一个独一无二的高度 Hi,其中Hi是1到N 之间的正 整数。 如果一段山脉比所有与它相邻的山脉都高,则这段山脉是一个山峰。位于边 缘的山脉只有一段相邻的山脉,其他都有两段(即左边和右边)。 类似地,如果一段山脉比所有它相邻的山脉都低,则这段山脉是一个山谷。 地精们有一个共同的爱好——饮酒,酒馆可以设立在山谷之中。地精的酒馆 不论白天黑夜总是人声鼎沸,地精美酒的香味可以飘到方圆数里的地方。 地精还是一种非常警觉的生物,他们在每座山峰上都可以设立瞭望台,并轮 流担当瞭望工作,以确保在第一时间得知外敌的入侵。 地精们希望这N 段山脉每段都可以修建瞭望台或酒馆的其中之一,只有满足 这个条件的整座山脉才可能有地精居住。 现在你希望知道,长度为N 的可能有地精居住的山脉有多少种。两座山脉A 和B不同当且仅当存在一个 i,使得 Ai≠Bi。由于这个数目可能很大,你只对它 除以P的余数感兴趣。

Input

仅含一行,两个正整数 N, P。

Output

仅含一行,一个非负整数,表示你所求的答案对P取余 之后的结果。
 
题解:
  三方dp
#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
int n,mod;
int f[4205][2][4205],ans;
int main(){
    n=read(),mod=read();
    f[2][1][2]=f[2][0][1]=1;
    f[3][0][1]=f[3][0][2]=f[3][1][2]=f[3][1][3]=1;
    register int i,j,k;
    for(i=4;i<=n;i++)
    for(j=1;j<=i;j++){
        for(k=1;k<j;k++) f[i][1][j]=(f[i][1][j]+f[i-1][0][k])%mod;
        for(k=j;k<i;k++) f[i][0][j]=(f[i][0][j]+f[i-1][1][k])%mod;
    }
    for(i=1;i<=n;i++) ans=(ans+f[n][0][i]+f[n][1][i])%mod;
    printf("%d\n",ans);
    return 0;
}

 

  First : 对于每一个 数字 i 和 i+1 , 如果这两个数不是相邻的,那么交换两个数字的对应的方案数是一样的

  Second: 我们由 1~n 的波动数列的任意一种,将每一个 ai 都可以用(n+1) 减去,那么得到的新的序列其实还是合法的,而且相对的山谷和山峰会改变!

      比如有 波动序列 32415 和 34251

  Third : 波动数列具有对称性......(那不是废话)

  DP[i][j]表示 选 1 To i 这些数字,第一个数为山峰,且第一个数为 j;答案就是 ∑ DP[N][j] (j = 1 to N)

  DP[i][j]=DP[i][j-1]+DP[i-1][i-j+1]

 

  证明:由性质一可知,当j与j-1不相邻的时候,j作为头所有的方案数与j-1作为头的方案数相同,于是就有DP[I][J]=DP[I][J-1];

  对于DP[i][j]+=DP[i-1][i+j-1];就是当j 与 j-1相邻时的情况;

  我们可以这么想,我第一个数选择了J 并且定义为山峰,那我第二个数j-1必定为山谷,后面的数属于[1,j-1]和[j+1,i];

  此时问题转化成了求 i-1个数,j-1为头,但是j-1 为山谷的方案数,由性质2可知,j-1作山谷和作山峰的方案数相同;

  现在的问题就是,此时的区间和我DP方程的区间意义不同;

  没关系;因为山峰与山谷是相对位置关系,将[j+1,i]区间的每个数都减一,这样是不改变相对大小关系的,并且此时就符合我们的方程了;

  另外,我DP[i-1][j-1]表示的是J-1为山顶时的个数,为了让其表示J-1为山谷的情况,要改成DP[i-1][(i-1+1)-(j-1)];

  这样就得到了我们的转移方程,我们可以用滚动数组优化空间;

代码:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
int f[2][4205];
int n,mod,ans;
int main(){
    n=read(),mod=read();    
    f[0][2]=1;
    for(int i=3;i<=n;i++)for(int j=2;j<=i;j++)
       f[i&1][j]=(f[i&1][j-1]+f[(i-1)&1][i-j+1])%mod;
    ans=0;
    for(int j=2;j<=n;j++)ans=(ans+f[n&1][j])%mod;    
    printf("%d",(ans<<1)%mod);
    return 0;
}

来自PaperCloud的博客,未经允许,请勿转载,TKS!

 

posted @ 2018-05-15 18:28  PaperCloud  阅读(155)  评论(0编辑  收藏  举报