动态规划训练之六
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;
}