时间限制:1秒
空间限制:32768K
度度熊最近对全排列特别感兴趣,对于1到n的一个排列,度度熊发现可以在中间根据大小关系插入合适的大于和小于符号(即 '>' 和 '<' )使其成为一个合法的不等式数列。但是现在度度熊手中只有k个小于符号即('<'')和n-k-1个大于符号(即'>'),度度熊想知道对于1至n任意的排列中有多少个排列可以使用这些符号使其为合法的不等式数列。
此题我的思路是用暴力思想来解,结果超时了。。。。。。
输入描述:
输入包括一行,包含两个整数n和k(k < n ≤ 1000)
输出描述:
输出满足条件的排列数,答案对2017取模。
输入例子:
5 2
输出例子:
66
先说说暴力解法:
先将每一种排列的所有数加入到一个数组链表中,再将该数组链表加入到大的数组链表中,最后遍历打的数组列表,如果符合不等式条件,即排列中相邻两个数符合小于不等式的有k个,符合大于不等式的有n-k-1个,这样这个排列是符合要求的,结果加1;直到遍历完整个大数组列表。
代码如下:
import java.util.ArrayList;
import java.util.Scanner;
public class Main {
public static ArrayList<ArrayList<Integer>> list = new ArrayList<>();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int n = sc.nextInt();
int k = sc.nextInt();
int[] a = new int[n];
int result = 0;
for (int i = 0; i < n; i++) {
a[i] = i+1;
}
int res = 0;
permutation(a, 0, n - 1);
for (ArrayList<Integer> alist : list) { //对所有排列进行遍历
int dayu = 0;
int xiaoyu = 0;
int j = 0;
int num = alist.size();
int m = 1;
while (j < num && m < num) { //对当前的排列进行遍历,判断排列中的满足不等式的数量
if (alist.get(j) < alist.get(m)) {
xiaoyu ++;
} else {
dayu ++;
}
j ++;
m = j + 1;
}
if (xiaoyu == k && dayu == n - k - 1) {
res++;
}
}
if (res > 0) {
result = res % 2017;
System.out.println(res);
}
}
sc.close();
}
public static void permutation(int[] arr, int start, int end) {
if (start == end) { // 到了末尾,将排列的字符串添加到链表中
ArrayList<Integer> alist = new ArrayList<>();
for (int i = 0; i <= end; i++) {
alist.add(arr[i]);
}
list.add(alist);
} else {
for (int i = start; i <= end; i++) { // 固定第一个字符不动
int temp = arr[start]; // 第一次start=1,i=1
// 排列时都是自己跟自己交换位置,当i=2时,第一个字符跟第二个字符交换位置
arr[start] = arr[i];
arr[i] = temp;
permutation(arr, start + 1, end); // 对第二个字符开始排列,递归对后面的字符串进行排列
temp = arr[start]; // 后面字符串排列结束后恢复为排列之前的顺序
arr[start] = arr[i];
arr[i] = temp;
}
}
}
}
在聊聊动态规划解法(网上学习的):
递归公式:dp[i][j] = (dp[i - 1][j - 1] * (i - j) + dp[i - 1][j] * (j + 1)) % 2017;
dp[i][j]表示有i个数字及j个小于号
所能组成的数量
(大于号数量当然是i - j - 1次,后面需要使用)
而加入第i + 1个数字时,分以下四种情况:
1.如果将i+1插入当前序列的开头,即有了1<2,加入后成为3>1<2,会发现等于同时加入了一个大于号!(此时可以无视1与2之间的关系,因为i+1>i)
2.如果将i+1插入当前序列末尾,即1<2变成了 1<2<3,会发现等于同时加入了一个小于号!
(此时可以无视1与2之间的关系,因为i+1>i)
3.如果将i+1加入一个小于号之间,即已经有 1<2了,向中间加入3,会发现变成了1<3>2,等于同时加入了一个大于号!
4.如果将i+1加入一个大于号中间,即有了2>1,变成了2<3>1,等于同时加入了一个小于号!
综上所述,
dp[i][j]等于以上四种情况之和:
dp[i - 1][j] //将i加在开头等于加入一个大于号,即要求i-1个数时已经有了j个小于号
dp[i - 1][j - 1] //将i加在末尾等于加入一个小于号,即要求i-1个数时已经有了j-1个小于号
dp[i - 1][j] * j //将i加在任意一个小于号之间,等于加入了一个大于号;即要求i-1个数时已经有了j个小于号,每个小于 号都可以进行这样的一次插入
dp[i - 1][j - 1] * (i- j - 1) //将i加载任意一个大于号之间,等于加入了一个小于号;即要求i-1个数时有了j-1个小于号,而此时共有
(i - 1) - (j - 1)- 1个大于号,每个大于号都要进行一次这样的操作
合并同类项即为
dp[i][j] = (dp[i - 1][j - 1] * (i - j) + dp[i - 1][j] * (j + 1))
最后要记得取模。。。
代码如下:
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
int n = sc.nextInt();
int k = sc.nextInt();
int[][] dp = new int[n+1][k+1];
for(int i=1;i<n+1;i++) dp[i][0] = 1;
for(int i=2;i<n+1;i++){
for(int j=1;j<k+1;j++){
dp[i][j] = (dp[i-1][j]*(1+j)+dp[i-1][j-1]*(i-j))%2017;
}
}
System.out.println(dp[n][k]);
}
}