题目描述
传说很久以前,大地上居住着一种神秘的生物:地精。 地精喜欢住在连绵不绝的山脉中。具体地说,一座长度为 N 的山脉 H可分 为从左到右的 N 段,每段有一个独一无二的高度 Hi,其中Hi是1到N 之间的正 整数。 如果一段山脉比所有与它相邻的山脉都高,则这段山脉是一个山峰。位于边 缘的山脉只有一段相邻的山脉,其他都有两段(即左边和右边)。 类似地,如果一段山脉比所有它相邻的山脉都低,则这段山脉是一个山谷。 地精们有一个共同的爱好——饮酒,酒馆可以设立在山谷之中。地精的酒馆 不论白天黑夜总是人声鼎沸,地精美酒的香味可以飘到方圆数里的地方。 地精还是一种非常警觉的生物,他们在每座山峰上都可以设立瞭望台,并轮 流担当瞭望工作,以确保在第一时间得知外敌的入侵。 地精们希望这N 段山脉每段都可以修建瞭望台或酒馆的其中之一,只有满足 这个条件的整座山脉才可能有地精居住。 现在你希望知道,长度为N 的可能有地精居住的山脉有多少种。两座山脉A 和B不同当且仅当存在一个 i,使得 Ai≠Bi。由于这个数目可能很大,你只对它 除以P的余数感兴趣。
输入格式
仅含一行,两个正整数 N, P。
输出格式
仅含一行,一个非负整数,表示你所求的答案对P取余 之后的结果。
样例
样例输入
4 7
样例输出
3
数据范围与提示
对于 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。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
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\n",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\n",ans); 31 } 32 /* 33 g++ 1.cpp -o 1 34 ./1 35 10 101520127 36 */