https://www.luogu.org/problem/P2467
这是一道好题
题目描述
求1-n排列组成的波动数列的个数
分析首先肯定是个dp没错了,考虑设计方案,
dp[i,j],表示用1-i的排列最后一个为j的方案数
dp[i,j]相当于dp[i-1,k]中原排列大于等于j的数都加1,再把j插到末尾后的新合法排列的方案数
类似test10.7的排列题
答案有“M"型与"W"型,显然方案数是一样的,这里只考虑"W"型的,最后把答案*2就行了

这时你可能会有疑问,为什么偶数是枚举[1,j-1],而奇数是枚举[j,i-1],
因为只考虑“W”形态的,所以奇数一定是山峰的,而偶数一定山谷;
所以奇数枚举的一定要比前一个位置上的数大,偶数枚举的一定要比前一个位置上的数小
#include<iostream> #include<cstdio> #include<cstring> using namespace std; #define N 4211 #define M(a) ((a)<=mod?(a):(a-mod)) inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-')f=-1; c=getchar(); } while(c>='0'&&c<='9'){ x=(x<<3)+(x<<1)+c-'0'; c=getchar(); } return x*f; } int n,mod; int dp[N][N],ans=0; int main(){ n=read(),mod=read(); for(int i=1;i<=n;i++){ dp[1][i]=1; } for(int i=2;i<=n;i++){ for(int j=1;j<=n;j++){ if(i&1){ for(int k=j;k<i;k++){ dp[i][j]=M(dp[i][j]+dp[i-1][k]); } } else{ for(int k=1;k<j;k++){ dp[i][j]=M(dp[i][j]+dp[i-1][k]); } } } } for(int i=1;i<=n;i++){ ans=M(ans+dp[n][i]); } cout<<2*ans%mod<<endl; return 0; }
下面的代码是用树状数组维护的,实际上可以用前缀和就好(代码不是我写的,不然我肯定前缀和)
还有就是要开O2才能过
code by std:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; #define N 4211 #define M(a) ((a)<=mod?(a):(a-mod)) inline int read(){ int x=0,f=1; char c=getchar(); while(c<'0'||c>'9'){ if(c=='-')f=-1; c=getchar(); } while(c>='0'&&c<='9'){ x=(x<<3)+(x<<1)+c-'0'; c=getchar(); } return x*f; } int n,mod; int dp[N][N],ans=0; int b[N]; int lowbit(int x){ return x&(-x); } void Add(int x,int d){ while(x<=n){ b[x]=M(b[x]+d); x+=lowbit(x); } } int Ask(int x){ int ans=0; while(x){ ans=M(ans+b[x]); x-=lowbit(x); } return ans; } int main(){ n=read(),mod=read(); for(int i=1;i<=n;i++){ dp[1][i]=1; Add(i,1); } for(int i=2;i<=n;i++){ for(int j=1;j<=n;j++){ if(i&1){ if(i>j){ dp[i][j]=M(Ask(i-1)-Ask(j-1)+mod); } } else{ dp[i][j]=Ask(j-1); } } memset(b,0,sizeof(b)); for(int j=1;j<=n;j++){ Add(j,dp[i][j]); } } for(int i=1;i<=n;i++){ ans=M(ans+dp[n][i]); } cout<<2*ans%mod<<endl; return 0; }