题目描述:
[HNOI2010]合唱队 - 洛谷https://www.luogu.com.cn/problem/P3205
题目信息提取:
?????????这个题目要求我们按照某种顺序将这个队伍中的所有人按照从左到右的顺序去排队,其实题目中的很多无用信息可以忽略,总结为,寻找某种数列,将这个数列按照从左到右的顺序依次进行排列,排列规则是,如果当前数字大于前一个已经排列完的数,那么他就放在新的当前排列的数列的最后面,如果他小于前一个已经排列玩的数字,那么他就放在当前排列的数列的最前面,看下面一个例子:
例如 初始数列为 33 22 11 44 那么他的排列顺序就是
因为22<33 所以 22 33
因为11<22 所以 11 22 33
因为44>11 所以 11 22 33 44
题目思路归纳:
? ? ? ?我们了解了基本的规则以后,看题目,题目给出一个期望得到的排列后的数列,让我们求出有几种默认的数列排放顺序可以经过排序后得到期望数列。所以我们可以考虑假如 我们要求的 期望数列为 11 22 33 44既然是动态规划,我们将应该先去总结出有无状态之间的转换关系,即状态转移方程,11 22 33 44的初始队列数量如何从一个子结构转化出来,11 22 33 44 他既可能是 22 33 44 加入了11也可能是11 22 33 加入了44,只有这两种可能,因为最新加入的数字一定在队列的最左侧或者是最右侧这句话是重点所以标红,所以我们知道了状态转移的规则,即当前状态一定是从他的左侧不包含最左侧数字的最优子结果和右侧不包含右侧数字的最优子结果转化而来的,这句话的理解非常重要,但是他是如何转化的呢?
????????还是看11 22 33 44 这个例子假如 11 22 33 44 是从11 22 33转化来的,那么数列最右侧的数字一定大于他的前一个数字,否则不成立,即不可能转化,那么他的前一个数字会在哪呢,其实同理就知道,他的前一个数字肯定就是11,33其中的某一个,这两种情况都可能如果是11先排序然后44排序,可以看到情况符合条件是成立的,例如是33排序后44排序,情况也是成立的,所以这两种情况都可以排列出我们要求的顺序,所以这两种情况相加就是44从右侧加进来的情况的数量,那么还有一种情况就是从22 33 44转化来的,即11是最后排序的,那么我们再来讨论11,11也有两种情况,即22和44都有可能是在11之前加入的,假如11小于22,那么22是最后加入的情况也是成立的,假如11小于 44,那么11最后加入的情况也是成立的,我们可以看到当前的数列也是满足这个要求的,所以同理11最后加入的情况的数量就等于22加入的数量加上44加入的数量之和,我们讨论完了这两种情况,不存在其他的情况了,所以11 22 33 44 的所有初始队列种类就是两者之和:我们有了思路就可以总结转台转移方程了,
这里我们使用两个数组来表示left[i][j]表示在下标i-j的这个数列中,最后加入的是i的种类数目而right[i][j]反之.
状态转移方程:
left[i][j] += left[i+1][j] when num[i] < num[i+1] eg:num是期望数列
left[i][j] += right[i+1][j] when num[i]<num[j]?
right[i][j] += right[i][j-1] when num[j]>num[j-1]
right[i][j] += left[i][j-1] when num[j]>num[i]?
代码书写:
? ? ? ? 我们知道了状态转移方程也就基本知道了代码结构了,但是还有一些细节需要注意,例如状态的初态和循环测顺序,首先我门看循环顺序,我们要确定循环的顺序首先看状态转移方程中状态之间的关系,在第一个和第二个状态转移方程中我们都需要i+1时的状态,所以我们可以确定i需要逆序递减求值,在2,3中可以看到都需要j-1的状态所以可以确定j需要递增,所以我们可以确定i的循环要从n开始逐渐递减,j要从i+1开始逐渐递增,来不断的扩展子状态的范围,然后我们需要确认初态,我们要给left[i][j]或者是right[i][j]其中的任何一个赋值1以确保任意值不会为0,只能赋值其中一个,因为当只有一个数时,它只能是从左边进或者是右边进,互相矛盾,所以最终的代码为:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
int n = Integer.parseInt(reader.readLine());
String[] intStr = reader.readLine().split(" ");
int[] ans = new int[n];
for(int i = 0;i<n;i++){
ans[i] = Integer.parseInt(intStr[i]);
}
int[][] left = new int[n][n];
int[][] right = new int[n][n];
for(int i = 0;i<n;i++){
left[i][i] = 1;
}
for(int i = n-1;i>=0;i--){
for(int j = i+1;j<n;j++){
if(ans[i]<ans[j])
left[i][j] +=right[i+1][j];
if(ans[i]<ans[i+1])
left[i][j] +=left[i+1][j];
if(ans[j]>ans[j-1]){
right[i][j]+=right[i][j-1];
}
if(ans[j]>ans[i])
right[i][j] +=left[i][j-1];
right[i][j]%=19650827;
left[i][j]%=19650827;
}
}
System.out.println((left[0][n-1]+right[0][n-1])%19650827);
}
}
|