P2467 [SDOI2010]地精部落

P2467 [SDOI2010]地精部落

1.该题难度挺大的,题解都很难看懂。

2.http://blog.csdn.net/mrazer/article/details/53426415?locationNum=12&fps=1此博文写得不错,摘抄如下,并加入自己的理解:

以样例1为例

1324   1423    2314   2413   2143

4231   4132    3241   3142   3412

引理一:一个抖动序列的连续子序列(≈一个数列的子串?)仍然是抖动序列。 
证明:根据抖动序列的定义即可得证。。。 

以 1324 为例:132   324 仍是抖动序列


引理二:若一个抖动排列中 x x+1 不相邻,那么交换 x x+1 ,序列仍然是抖动序列。 
证明:(请自己在脑海中感性证明)由于 x x+1 不相邻,所以与这两个元素相邻的最多4个元素 [1,x1]|[x+2,max] ,所以易知差距至少为2,经过一些简单的分情况讨论后考虑与相邻元素的大小关系可以发现原谷仍谷,而原峰仍峰,所以命题得证。 

以 1324 1423 为例:1423 即是由 1324 交换 3 4 而来的

以 2143 为例:2143 因 2 1 相邻 4 3 相邻,故无法生成新的抖动序列


引理三:若使一个抖动排列中大于等于 x 的元素全部加1,得到的序列仍为抖动序列。 
证明:这个可以考虑归纳法证明,对 x 进行归纳, x=1 时相当于把整体加1,大小关系不变所以基础显然,把 x 加1,仍然是由于差距至少为2,分一下情况就可以发现大小关系仍然合法,命题得证(感觉应该不算很难,大概写写画画就出来了,所以写得简略了一点) 


引理四:一个 [1,X] 的抖动序列一定可以对应到 [YX+1,Y] 的一个抖动序列。 
证明:用 Yx+1 替换掉 x 就好了,并且易知如果采用这种方法的话这是一一对应的,并且峰谷交换了。

该引理要表达的意思是,用n+1减去抖动序列每项,可以得到新的抖动序列

以   1324   4231 为例:5 减去 1324 每项可得 4231

有了上述几个引理我们就可以愉快地DP了! 
f[i][j] [1,i] 的排列中开头第一个位置的元素(其实当然也可以表示是结尾啦~)恰为 j j 为峰(即第一个数 j 大于第二个数)的抖动排列的方案数。既然已经确定了第一个数,那么不妨考虑第二个数。 
(1)如果第二个数不是 j1 。那么根据引理二,我们总可以交换当前第一个元素 j 与和它不相邻的元素 j1 而保持序列仍是抖动序列,并且由于只是进行了交换,所以是一一对应的。这一部分的方案数是 f[i][j1] 。 
(2)如果第二个数是 j1 。那么去掉第一个数 j 后, [1,i] 还剩下 [1,j1]|[j+1,i] ,根据引理三,我们只需要保持从第二个位置开始的序列是一个排列范围是 [1,i1] 的抖动序列然后把其中 [j,i1] 的这一部分整体加1就得到一个 [1,i] 的抖动序列了(并且也可以知道这一部分是一一对应的),第一个数 j 是峰,那么第二个数 j1 就应该是谷咯,可我们的状态里似乎并没有谷?使用引理四!这时再结合上文,我们可以得到这一部分的方案数其实就是 f[i1][(i1)(j1)+1]=f[i1][ij+1] (其中第一维是用了上文的对应,而第二维是利用了引理四中的对应,所以整体也是一一对应的)。

该点理解:1 2 3 ... j-2 j-1 j+1 j+2 ...i 根据数据的离散化 [j+1,i] 区间整体 -1 可得1 2 3 ... j-2 j-1 j j+1 ...i-1即变成[1,i-1]排列范围。

j峰 j-1谷 故 接下来是一个上升序列

好了,这上面两种情况已经包含了第二个位置上的数的所有情况,并且都是一一对应的,根据动态规划与计数问题的归纳证明,我们成功地得到了正确的转移方程: f[i][j]=f[i][j1]+f[i1][ij+1] ! 
最终 ans=(i=1nf[n][i])2 (其实开头元素为1且是峰的根本不存在好吧,所以我们当然可以直接不转移它啦~)(记得特殊处理一下 n=1 的情况),最后那个 2 是什么意思呢?很简单,因为我们状态里只有第一个数是峰的情况,根据引理四中的一一对应与峰谷交换,我们 2 就可以得到第一个数是谷的情况的方案数了。 
当然接下来我们要做的就是简单的改成滚动数组就可应对内存的问题了。

以下为AC代码,2017-9-14 21:45

#include <stdio.h>
#include <string.h>
int f[2][4210];//滚动数组,技巧性高。 f[0] f[1]交替使用
int main(){
    int now=0,i,j,n,ans=0,mod;
    memset(f,0,sizeof(f));
    scanf("%d%d",&n,&mod);
    f[now][2]=1;//即f[2][2]=1 "2 1" 一维数组表示
    for(i=3;i<=n;i++){
        now^=1;
        for(j=2;j<=i;j++)
            f[now][j]=(f[now][j-1]+f[now^1][i-j+1])%mod;//f[i][j]=f[i][j-1]+f[i-1][i-j+1]故前两项用now 最后一项用now^1
    }
    for(i=2;i<=n;i++)
        ans=(ans+f[now][i])%mod;
    ans=(ans*2)%mod;
    printf("%d\n",ans);
    return 0;
}



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值