• [SDOI2010] 地精部落 组合数+DP


    题目描述

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

    输入格式

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

    输出格式

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

    样例

    样例输入

    4 7

    样例输出

    3

    数据范围与提示

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

     思路:

      这题就居然被分到了组合数学,还日了我两天,其实这是个计数类DP题目。

      计数类DP其实是把所有的情况按一标准分类,然后用dp数组的状态进行统计,

      首先总结一下题意就是满足长度为n的锯齿状序列(不能连续3个以上单调)。本剧若 蒟篛觉着非完美算法还是很重要的,毕竟在考场上难以一下想到正解,暴力及其他也很重要,观察数据范围分的明显,所以在此扯一些部分算法。

      20%算法:next_permutation 咳咳

      40%算法: 题做不出来的原因在于需要维护的信息过多,例如我要转移就要找之前的合法数和最后一位。那么看到n<=18可以考虑状压,多维护点信息 f[s][j][k],表示状态为s(记录每个长度是否考虑过),最后一位高度为j,山峰或山谷(k) 的方案数。那么转移就显然了。就用长度为1的序列数一个一个往后怼,把状态一个一个扩展

    f[s|(1<<(i-1))][j][k]=f[s][j][k]+∑f[s][p][k^1] (if(山峰) p<j else(山谷) p>j )     ∑f[1<<(n-1)-1][j][op]就是答案。

      70%算法:其实已经是正解思路了。我们在状压的时候发现其实状态的保留没什么屎用,重新观察n<=4200,九成是n^2的空间复杂,n^2时间复杂,尝试设f[i][j][k]为当我考虑有长度为i的区间,最后一位为j,是山峰或者山谷的方案数。那么就考虑山峰是找比j小的加和,山谷是找比j打的加和,恩没啥问题,于是我信誓旦旦的打完了,然后良心样例叫我做人。。。恩对他其实是错的,看一个例子:长度为2,最后为2,为山峰的方案数是 f[2][2][1]=1;(1,2),然后会对f[3][1][0]做贡献,序列就是这样(1,2,1)。。。。然后探索。。。。发现其实序列里的数谁是谁不重要,最后一个排名多少比较重要,然后就可以重新设为最后一位排名为j的方案数,这样原来f[2][2][1]=1虽然也会做贡献,但是对f[3][1][0]的贡献就是(2,3,1) 之前不一定是1,2,而是排名为1,2。转移是f[i][j][1]+=f[i-1][k][0]   f[i][j][0]+=f[i-1][k][1];最后答案是∑f[n][i][1]+f[n][i][0];其实本质上就是把长度为i-1的序列离散化,然后挨个往后怼数,序列一定是一一对应的,答案一定是正确的。

      100%算法:就是对70的优化,会发现转移的复杂度是O(n)的,要优化只需要加一些常规操作前缀和以及滚动数组的优化。  

      终于结束啦!!

      另外一定要注意模意义下相减可能有负数,最后最好写上(ans+mod)%mod。

      

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<algorithm>
     4 using namespace std;
     5 int n,p,ans=0;
     6 int f[2][4320][2],sum[2][4320][2];
     7 int main()
     8 {
     9     scanf("%d%d",&n,&p);
    10     f[2&1][2][1]=f[2&1][1][0]=1;
    11     sum[2&1][2][1]=sum[2&1][1][0]=1;sum[2&1][2][0]=1;
    12     for(int i=3;i<=n;i++)
    13     {
    14         for(int j=1;j<=i;j++)
    15         {
    16             f[i&1][j][0]=(sum[i&1^1][i-1][1]-sum[i&1^1][j-1][1])%p;
    17             f[i&1][j][1]=(sum[i&1^1][j-1][0])%p;
    18             sum[i&1][j][0]=(sum[i&1][j-1][0]+f[i&1][j][0])%p;
    19             sum[i&1][j][1]=(sum[i&1][j-1][1]+f[i&1][j][1])%p;
    20             //printf("%d %d %d %d
    ",i,j,f[i][j][0],f[i][j][1]);
    21         }
    22     }
    23     int ans=0;
    24     for(int j=1;j<=n;j++)
    25     {
    26         ans+=f[n&1][j][0]+f[n&1][j][1];
    27         ans%=p;
    28     }
    29     while(ans<0) ans+=p;
    30     printf("%d
    ",ans);
    31 }
    32 /*
    33 g++ 1.cpp -o 1
    34 ./1
    35 10 101520127
    36 */
    View Code
    Zeit und Raum trennen dich und mich.时空将你我分开。
  • 相关阅读:
    iOS 中架构模式的浅显理解
    Block 在 ARC 下的拷贝
    Repo 的使用小结
    博客园新语言代码高亮以及OpenLiveWriter插件开发(一)
    GridLayout 使用
    PopupWindow 使用
    NDK笔记(二)-在Android Studio中使用ndk-build
    NDK 笔记(一)
    Java 多线程编程
    搭建Apache Web服务器
  • 原文地址:https://www.cnblogs.com/starsing/p/11126421.html
Copyright © 2020-2023  润新知