• [sdoi 2010][bzoj 1925]地精部落(dp)


    题干:

    地精喜欢住在连绵不绝的山脉中。一座长度为N的山脉 H可分 为从左到右的N段,每段有一个独一无二的高度 Hi,其中Hi是1到N之间的正整数。 如果一段山脉比所有与它相邻的山脉都高,则这段山脉是一个山峰。位于边 缘的山脉只有一段相邻的山脉,其他都有两段(即左边和右边)。如果一段山脉比所有它相邻的山脉都低,则这段山脉是一个山谷。酒馆可以设立在山谷之中。地精还是一种非常警觉的生物,他们在每座山峰上都可以设立瞭望台,地精们希望这N段山脉每段都可以修建瞭望台或酒馆的其中之一,只有满足这个条件的整座山脉才可能有地精居住。 现在你希望知道,长度为N 的可能有地精居住的山脉有多少种。两座山脉A 和B不同当且仅当存在一个i,使得 Ai≠Bi。由于这个数目可能很大,你只对它除以P的余数感兴趣。

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

    题解:

    首先,我们的目标序列一定是山峰与山谷交替出现的,也就是说,当x位置是山峰,我们就要从剩下的且比x处山峰高度小的数中挑一个,放到x+1位置上,反之要挑大的数。根据题干中的“连绵不绝的山脉”,我们可以知道1~n中的所有数在n个山峰中都出现且仅出现一次。。。(一本正经地说这么显然的东西。。。)既然是“连绵不绝”,我们手模几个小样例就可以发现

    0%输出样例

    40%状压
      先看一下数据范围,发现40%的点其实是可以作为状压dp来写,但dp表示什么呢?一般来说,状压dp[i][j]表示枚举到第i位时状态为j时的方案数(本题所求的是方案数)。而到本题适用吗?想一下:枚举到第i位时我安置的房子状态为j。。。好像是可以,但状态怎么表示?010101或101010?不可取。反正本蒟蒻作者没有想到,咕咕咕。。。(40%的算法都想不到。。。)其实dp[i][j][0/1]状态无非就是:j中0表示未选这个数,1表示已选这个数;第三维中0表示山谷,1表示山峰。那么dp[i][j][1]+=dp[i-1][j^k][0];(k表示未选过的并比现在选择的数小的数);dp[i][j][0]+=dp[i-1][j^k][1];(k表示未选过的并比现在选择的数大的数)。

    70% O(n3)

      5503勉强能承受。我们记录状态为dp[i][j][0/1],i表示剩下i个数,j表示有j-1个数小于刚刚选择的数,当下标为0时,表示山谷,当下标为1时,表示山峰(其实正反都可以)。根据我们所定义的转移方程,dp[i][j][0]可以影响dp[i-1][j~i][1],dp[i][j][1]可以影响dp[i-1][1~j-1][0]。那么我们就可以初始化dp[n][0][0]=dp[n][n+1][1]=1(刚开始所有的数都可以作为第一个数为山峰或山谷,要么没有数比上一层大,要么所有数都比上一层大),最后求dp[0][1][0]+dp[0][1][1]即可。

    100% O(n2)

      其实在n3的算法中,我么没事在不断刷表,一个一个加。我们可以将刷表改为填表,比较容易地就可以发现dp[i][j][1]=Σjx=0 dp[i+1][x][0];dp[i][j][0]=Σi+2x=j+2 dp[i+1][x][1](其中的Σ可以在枚举的过程中顺便求得),加上滚动数组,完美

    Code:

     1 #include<cstdio>
     2 #include<cstring>
     3 using namespace std;
     4 const int maxn=4200+10;
     5 int dp[2][maxn][2],n,mod,cur;
     6 int main(){
     7     scanf("%d%d",&n,&mod);
     8     memset(dp,0,sizeof(dp));
     9     dp[cur][0][0]=dp[cur][n+1][1]=1;
    10     for(register int i=n-1;i>=0;--i){
    11         cur^=1;
    12         for(register int j=0;j<=i+2;++j) dp[cur][j][0]=dp[cur][j][1]=0;
    13         int x=dp[cur^1][0][0];
    14         for(register int j=1;j<=i+1;++j){
    15             x=(x+dp[cur^1][j][0])%mod;
    16             dp[cur][j][1]=(dp[cur][j][1]+x)%mod;
    17         }
    18         x=dp[cur^1][i+2][1];
    19         for(register int j=i+1;j>=1;--j){
    20             dp[cur][j][0]=(dp[cur][j][0]+x)%mod;
    21             x=(x+dp[cur^1][j][1])%mod;
    22         }
    23     }
    24     printf("%d",(dp[cur][1][0]+dp[cur][1][1])%mod);
    25 }
    Code

    100% O(n2) plus

      我们还可以设dp[i][j]为已经有i个数,且首项为j且j为波峰的方案总数。既然确定了首项,我们在来考虑第二项,若第二项不为j-1,那么我们将这两个数换位发现仍然为抖动数列(j为首项且为波峰),我们把所有这种情况下的序列都交换j与j-1的位置,那么这种情况方案数为dp[i][j-1]。我们在来考虑第二项为j-1的情况,这种情况就比较麻烦,我们去掉第一个数j后,所剩下的就只有[1~j-1]和[j+1~i],其实这i-1个数就便成了我们dp[i-1]的情况了(将[j+1~i]减1),那么就得到了一个[1~i-1]的抖动序列,再使j-1为波谷,此种情况方案总数为f[i-1][i-1-(j-1)+1]=f[i-1][i-j+1](我们手模几个点就可以发现,当我们将所有的数都由i变为n-i+1时同样成立-->波峰变波谷。因为对称性我们把ans×2,就得到了结果(不是在转移时就已经考虑了对称时的乘2了吗?为什么还要乘2呢?其实我们上文的转移是将那位的数变化了,是内部的变化,只是相当于波峰变波谷;而最后的乘2是对全局考虑,真正地将波峰变为波谷)。

    Code:

     1 #include<cstdio>
     2 #include<cstring>
     3 #define $ 4444
     4 using namespace std;
     5 int m,n,mod,dp[2][$],ans;
     6 signed main(){
     7     scanf("%d%d",&n,&mod);
     8     if(n==1){    printf("%d
    ",1%mod); return 0;    }
     9     dp[0][1]=1;
    10     for(register int i=2,now=1,las=0;i<=n;++i,las=now,now^=1){
    11         for(register int j=1;j<=i;++j)
    12             dp[now][j]=(dp[now][j-1]+dp[las][i-j+1])%mod;
    13     }
    14     for(register int i=1;i<=n;++i) ans=(ans+dp[(n-1)%2][i])%mod;
    15     printf("%d
    ",(ans<<1)%mod);
    16 }
    code

     

    越努力 越幸运
  • 相关阅读:
    HAProxy从零开始到掌握
    学会这15点,让你分分钟拿下Redis数据库
    求一个字符串长度
    js时间戳怎么转成日期格式
    js获取url参数值的两种方式
    js 处理URL实用技巧
    jQuery操作radio、checkbox、select 集合
    js处理url的技巧和归纳
    ajax hash缓存
    jquery ajax跨域
  • 原文地址:https://www.cnblogs.com/OI-zzyy/p/11131686.html
Copyright © 2020-2023  润新知