题目描述
输入格式
输出格式
样例
数据范围与提示
思路:
这题就居然被分到了组合数学,还日了我两天,其实这是个计数类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 */