[HNOI2010]CHORUS 合唱队 (区间DP)
题目描述
对于一个包含 NN 个整数的数列 AA ,我们可以把它的所有元素加入一个双头队列 BB .
首先 A1A1 作为队列的唯一元素,然后依次加入 A2∼ANA2∼AN ,如果 Ai<Ai−1Ai<Ai−1 那么从 BB 的左端加入 AiAi ,否则从 BB 的右端加入 AiAi .
给出最终的队列 BB ,求原数列有多少种可能排列。输出答案对 1965082719650827 取余 .
1≤N≤1000≤Bi≤20001≤N≤1000≤Bi≤2000 ,没有重复数 .
思路
显然我还是太菜了,被这道题虐了 ... 另外从大基哥那里搞了个清楚的题面(原题在此)过来.
读过题面之后,会发现,每一个状态的队列的前导状态只有两个,要么是第一个元素先进来,要么就是最后一个元素先进来.
这样子的话就比较容易想到区间DP的常用思路.
状态
f[i][j][1] 表示从 i 到 j 这段数列的最后一个进来的元素是 a[j] .
f[i][j][0] 则表示这段数列最后一个进来的元素是 a[i] .
然后就是 O(n^2) 的枚举区间.
动态转移方程的话,是根据它的前一步状态,分类讨论得.
主要思路就是讨论区间前后四个数的大小关系然后推得它可以由哪些状态推过来.
其实方程这个东西自己想一下还是对锻炼DP很有帮助的,建议自己想.
约束条件
约束条件就是所有枚举的 i j 与其相邻数值的大小比较 .
代码
#include<bits/stdc++.h> using namespace std; const int m=19650827; int n,f[1008][1008][2],a[1008]; int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]),f[i][i][0]=1; for(int i=n;i>=1;i--) for(int j=i+1;j<=n;j++) { if(a[j]>a[i]) f[i][j][1]+=f[i][j-1][0]; if(a[j]>a[j-1]) f[i][j][1]+=f[i][j-1][1]; if(a[i]<a[i+1]) f[i][j][0]+=f[i+1][j][0]; if(a[i]<a[j]) f[i][j][0]+=f[i+1][j][1]; f[i][j][0]%=m; f[i][j][1]%=m; } cout<<(f[1][n][0]+f[1][n][1])%m; return 0; }