洛谷P3205 [HNOI2010]合唱队
题目:
题目描述
为了在即将到来的晚会上有更好的演出效果,作为 A 合唱队负责人的小 A 需要将合唱队的人根据他们的身高排出一个队形。假定合唱队一共 n 个人,第 i 个人的身高为 hi 米(1000≤hi≤2000),并已知任何两个人的身高都不同。假定最终排出的队形是 A 个人站成一排,为了简化问题,小 A 想出了如下排队的方式:他让所有的人先按任意顺序站成一个初始队形,然后从左到右按以下原则依次将每个人插入最终棑排出的队形中:
第一个人直接插入空的当前队形中。
对从第二个人开始的每个人,如果他比前面那个人高(h 较大),那么将他插入当前队形的最右边。如果他比前面那个人矮(h 较小),那么将他插入当前队形的最左边。
当 n 个人全部插入当前队形后便获得最终排出的队形。
例如,有 6 个人站成一个初始队形,身高依次为 1850,1900,1700,1650,1800,1750
那么小 A 会按以下步骤获得最终排出的队形:
1850
1850,1900 ,因为 1900>1850。
1700,1850,1900,因为 1700<1900
1650,1700,1850,1900 ,因为 1650<1700
1650,1700,1850,1900,1800,因为 1800>1650
1750,1650,1700,1850,1900,1800,因为 1750<1800
因此,最终排出的队形是 1750,1650,1700,1850,1900,1800
小 A 心中有一个理想队形,他想知道多少种初始队形可以获得理想的队形。
请求出答案对 196508271 取模的值。
输入格式
第一行一个整数 n。
第二行 n 个整数,表示小 A 心中的理想队形。
输出格式
输出一行一个整数,表示答案 % 19650827 的值。
输入输出样例
输入 #1
4
1701 1702 1703 1704
输出 #1
8
思路:
第一次遇到多状态的区间DP题,思考时间有点长
由于总共有两种插入方式:大于前一数就放在右边,小于前一数就放在左边,由此可衍生出两种状态:新插入的数在最左边和新插入的数在最右边
因为求得是到达最后序列的方案,就可以这样想:有多少种可能是可以到达最后序列的,可能说的不太清楚,往下看
枚举一段区间i~j,新插入的数要么在最左端要么在最右端,所以f[0][i][j](0代表在左端插入,1相反,而f[0][i][j]则代表新插入的数在左端的所有可能序列)之前的数要么是原始序列最左端的数a[i+1]要么是最右端的数a[j],所以f[0][i][j]可以从这两个状态转移过来,前提是符合条件新插在左端a[i]要小于之前的数,f[1][i][j]同理
接着是初状态(貌似所有区间DP初状态都得想一会),i==j
肯定只有一种可能,所以把f[0][i][i]都置为1。为什么不把f[1][i][i]也置为1呢,因为i==j
代表只有一个人时的方案,只有一个人时只有一种可能,总方案数f[0][i][i]+f[1][i][i]=1
参考于洛谷第一篇博客
代码:
/*#!/bin/sh
dir=$GEDIT_CURRENT_DOCUMENT_DIR
name=$GEDIT_CURRENT_DOCUMENT_NAME
pre=${name%.*}
g++ -O2 $dir/$name -o $pre -g -Wall -std=c++11
if test $? -eq 0; then
gnome-terminal -x bash -c "time $dir/$pre;echo;read;"
fi*/
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int maxn=2e3+5,INF=0x3f3f3f3f;
int n,f[2][maxn][maxn],a[maxn];//0左 1右
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=s*10+ch-'0',ch=getchar();
return s*w;
}
int main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)f[1][i][i]=1;
for(int d=2;d<=n;d++){
for(int i=1,j;(j=i+d-1)<=n;i++){
if(a[i]<a[i+1])f[0][i][j]+=f[0][i+1][j];
if(a[i]<a[j])f[0][i][j]+=f[1][i+1][j];//左端插入的情况,共两种可能
if(a[j]>a[j-1])f[1][i][j]+=f[1][i][j-1];
if(a[j]>a[i])f[1][i][j]+=f[0][i][j-1];//右端插入的情况,共两种可能
f[1][i][j]%=19650827;
f[0][i][j]%=19650827;
}
}
cout<<(f[1][1][n]+f[0][1][n])%19650827;
}
over~