BZOJ 1925 地精部落 DP

Description

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

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

仅含一行,一个非负整数,表示你所求的答案对P取余 之后的结果。
Sample Input

4 7
Sample Output

3
HINT

对于 20%的数据,满足 N≤10;
对于 40%的数据,满足 N≤18;
对于 70%的数据,满足 N≤550;
对于 100%的数据,满足 3≤N≤4200,P≤109
Source

分析:本来想随便水一道的,结果翻车了,想了好久。一开始想直接f[i][1/0]转移的,0表示i点为峰,1表示为谷。后来想了想发现不对,这样扫一遍过去只能得到一种合法的摆列,因为题目有说高度属于1~n,于是行不通。现在考虑新的转移,4200的范围首先想到n^2,于是定义f[i][j],想想状态,我们要求长度为n的波动序列的排列方案,则i肯定是表示前i位(一般套路),考虑j,首先给出下列3个公理:
1.不相邻的x与x-1,交换位置后波动序列任然满足(自己yy)。
2.把序列里大于等于x的数加上任意数,序列任是波动。可以用一句话来“证明”,比你大还是比你大,你大爷还是你大爷。
3.对于一个1~m的波动序列,一定可以对应得到n-m+1~n的波动序列。(只需要用n-m+1~n,和1~m,一一替换就ok了)
有了上面的buff,现在考虑dp,首先可以想到既然要算1~m的序列,则一定要表示出m,于是可以推出第二维为第一个数大小用j表示。转移方程为:F[i][j]=f[i][j-1]+f[i-1][i-j+1],f[i][j-1]对应定理2,若第二位不是j-1则可以交换j-1和j,等效。f[i-1][i-j]表示去掉j之后剩i-1个数,要保证加上j后原序列任然波动,则根据定理4,推出f[i-1][i-j]。最后加个滚动数组优化一下就好。

# include <cstdio>
# include <cstring>
# include <cmath>
# include <algorithm>
using namespace std;
typedef long long ll;
ll read()
{
    register ll f=1,i=0;char c=getchar();
    while(c<'0'||c>'9') {if(c=='-'){f=-1;}c=getchar();}
    while(c>='0'&&c<='9') {i=(i<<3)+(i<<1)+c-'0';c=getchar();}
    return i*f;
}
const int N=5000;
int n,P,ans,f[2][N],now=1;
int main()
{
    n=read(),P=read();
    if(n==1){printf("%d\n",1%P);return 0;}
    f[1][1]=1;
    for (int i=2;i<=n;++i){
        for (int j=1;j<=i;++j)
            f[i&1][j]=(f[i&1][j-1]+f[(i-1)&1][i-j])%P;
    }
    printf("%d\n",(f[n&1][n]<<1)%P);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值