P3205 [HNOI2010]合唱队
P3205 [HNOI2010]合唱队
题目描述
为了在即将到来的晚会上有更好的演出效果,作为 \(AAA\) 合唱队负责人的小 \(A\) 需要将合唱队的人根据他们的身高排出一个队形。假定合唱队一共 \(n\) 个人,第 \(i\) 个人的身高为 \(h_i\) 米 \((1000 \le h_i \le 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\) 心中有一个理想队形,他想知道多少种初始队形可以获得理想的队形。
请求出答案对 \(19650827\) 取模的值。
输入格式
第一行一个整数 \(n\)。
第二行 \(n\) 个整数,表示小 \(A\) 心中的理想队形。
输出格式
输出一行一个整数,表示答案 \(\mod 19650827\) 的值。
输入输出样例
输入 #1
4
1701 1702 1703 1704
输出 #1
8
说明/提示
对于 \(30\%\) 的数据,\(n \le 100\)。
对于 \(100\%\) 的数据,\(n \le 1000\),\(1000 \le h_i \le 2000\)。
Solution
分析题目性质我们可以发现,随着越来越多的人加入到合唱队形中,小 \(A\) 的理想队形是逐渐向两端扩增的。
假如说我们已经排好了 \([l,r]\) 的队形,再加入一个人后的理想队形我们就排好了 \([l-1,r]\) 或 \([l,r+1]\),像这样,每一步操作都使我们的目标区间发生有规律的变化,我们应该用区间\(dp\)来解决。
因为题目中有在左边插入和在右边插入两种情况,所以我们开两个数组来分别记录两种情况。
状态设置:
\(f1[l][r]\) 我们已经排好了 \([l,r]\) 的理想队形,且最后一个人插在了最左边的方案数。\(f2[l][r]\) 为插在了最右边的方案数。
状态转移:
发现当前这个人是插在最左边和最右边取决于他与上个人的身高差,而上个人又分为最左边和最右边两种情况,所以我们要分四种情况讨论:
\(1.\) 当前的人比上一个人矮,上一个人被插在了最左边。故当前这个人需要插到最左边,即 \(l\) 的位置,且上一个人是 \(a[l+1]\),所以有 \(f1[l][r]+=f1[l+1][r](a[l]<a[l+1])\)。
\(2.\) 当前的人比上一个人矮,上一个人被插在了最右边。故当前这个人需要插在最左边,即 \(l\) 的位置,且上一个人是 \(a[r]\),所以有 \(f1[l][r]+=f2[l+1][r](a[l]<a[r])\)。
\(3.\) 当前的人比上一个人高,上一个人被插在了最左边。故当前这个人需要插在最右边,即 \(r\) 的位置,且上一个人是 \(a[l]\),所以有 \(f2[l][r]+=f1[l][r-1](a[r]>a[l])\)。
\(4.\) 当前的人比上一个人高,上一个人被插在了最右边。故当前这个人需要插在最右边,即 \(r\) 的位置,且上一个人是 \(a[r-1]\),所以有 \(f2[l][r]+=f2[l][r-1](a[r]>a[r-1])\)。
边界条件
排好一个人的方案数为 \(1\),但注意我们不能让 \(f1[i][i]\) 和 \(f2[i][i]\) 同时为 \(1\),因为你不能认为他既是从左边插入也是从右边插入,这样会算重。
答案输出
排完 \([1,n]\) 的理想队列,最后一个人插在左边和插在右边的方案数之和。
\(f1[1][n]+f2[1][n]\)。
\(Code\)
#include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
inline ll read()
{
char ch=getchar();
ll a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<1)+(a<<3)+(ch^48);
ch=getchar();
}
return a*x;
}
const ll N=1005;
const ll M=19650827;
ll n;
int a[N];
ll f1[N][N],f2[N][N];
int main()
{
n=read();
for(ll i=1;i<=n;i++) a[i]=read();
for(ll i=0;i<=n;i++) f2[i][i]=1;
for(ll len=2;len<=n;len++)
{
for(ll l=1;l+len-1<=n;l++)
{
ll r=l+len-1;
if(a[l]<a[l+1]) f1[l][r]+=f1[l+1][r];
if(a[l]<a[r]) f1[l][r]+=f2[l+1][r];
if(a[r]>a[l]) f2[l][r]+=f1[l][r-1];
if(a[r]>a[r-1]) f2[l][r]+=f2[l][r-1];
f1[l][r]%=M;f2[l][r]%=M;
}
}
printf("%lld\n",(f1[1][n]+f2[1][n])%M);
return 0;
}