《算法竞赛·快冲300题》将于2024年出版,是《算法竞赛》的辅助练习册。
所有题目放在自建的OJ New Online Judge。
用C/C++、Java、Python三种语言给出代码,以中低档题为主,适合入门、进阶。
【题目描述】 对于一个长度为n的排列a而言,如果不存在a[i]=i,则称之为一个错位排列。按照字典序输出前k个错位排列。
【输入格式】 输入包含两个正整数n和k。输入保证n不超过1000,n*k不超过100000。
【输出格式】 输出k行,每行n个整数表示一个排列。
【输入样例】
样例1:
2 1
样例2:
3 2
【输出样例】
样例1:
2 1
样例2:
2 3 1
3 1 2
在做本题之前,先回顾DFS的经典应用:按字典序输出n个数的全排列。
下面的DFS代码能从小到大打印排列。前提是数组a[20]中的数字是从小到大的,如果不是,先排序。用b[]记录一个新的全排列,第一次进入dfs()时,b[0]在n个数中选一个;第二次进入dfs()时,b[1]在剩下的n-1个数中选一个;…;直到选了n个数为止,就得到了一个全排列。用vis[]记录某个数是否已经被选过,选用过的数不能在后面继续选。
代码输出:“1 2 3 ; 1 3 2 ; 2 1 3 ; 2 3 1 ; 3 1 2 ; 3 2 1 ;”
#include
using namespace std;
int a[20]={1,2,3,4,5,6,7,8,9,10,11,12,13};
bool vis[20]; //记录第i个数是否用过
int b[20]; //生成的一个全排列
void dfs(int s,int t){
if(s==t) { //递归结束,产生一个全排列
for(int i=0; i<t; ++i) cout<<b[i]<<" "; //输出一个排列
cout<<"; ";
return;
}
for(int i=0;i<t;i++)
if(!vis[i]){
vis[i]=true;
b[s]=a[i];
dfs(s+1,t);
vis[i]=false;
}
}
int main(){
int n=3;
dfs(0,n); //前n个数的全排列
return 0;
}
【重点】 用DFS按字典序输出全排列。
回到本题,要求输出错位排列,只要对上面代码略作修改,跳过a[i]=i的排列即可。
#include
using namespace std;
const int N = 1010;
int n, k;
int b[N];
bool vis[N];
int num=0; //统计已经输出的全排列数量
void dfs(int s){
if(s == n) {
for(int i=0;i<n;i++) printf("%d ",b[i]);
printf("\n");
num++; //输出了一个排列,num数量加1
return;
}
if(num == k) return; //只输出前k个
for(int i=1;i<=n;i++) {
if(i == s+1) continue; //处理错位:跳过a[i]=i的情况
if(!vis[i]){
vis[i] = true;
b[s] = i;
dfs(s+1);
vis[i] = false;
}
}
}
int main(){
cin >> n >> k;
dfs(0); //前n个数的全排列
return 0;
}
输出全排列的代码
import java.util.*;
public class Main {
static int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
static boolean[] vis = new boolean[20];
static int[] b = new int[20];
public static void dfs(int s, int t) {
if (s == t) {
for (int i = 0; i < t; ++i) System.out.print(b[i] + " ");
System.out.print("; ");
return;
}
for (int i = 0; i < t; i++) {
if (!vis[i]) {
vis[i] = true;
b[s] = a[i];
dfs(s + 1, t);
vis[i] = false;
}
}
}
public static void main(String[] args) {
int n = 3;
dfs(0, n);
}
}
本题的代码
import java.util.*;
public class Main {
static int N = 1010;
static int n, k;
static int[] b = new int[N];
static boolean[] vis = new boolean[N];
static int num = 0; // 统计已经输出的全排列数量
public static void dfs(int s) {
if (s == n) {
for (int i = 0; i < n; i++) System.out.print(b[i] + " ");
System.out.println();
num++; // 输出了一个排列,数量加1
return;
}
if (num == k) return; // 只输出前k个
for (int i = 1; i <= n; i++) {
if (i == s+1) continue; // 处理错位:跳过a[i]=i不输出
if (!vis[i]) {
vis[i] = true;
b[s] = i;
dfs(s+1);
vis[i] = false;
}
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
k = sc.nextInt();
dfs(0); // 前n个数的全排列
}
}
输出全排列的代码
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]
vis = [False] * 20
b = [0] * 20
def dfs(s, t):
if s == t:
for i in range(t): print(b[i], end=" ")
print("; ", end="")
return
for i in range(t):
if not vis[i]:
vis[i] = True
b[s] = a[i]
dfs(s + 1, t)
vis[i] = False
n = 4
dfs(0, n)
本题的代码
N = 1010
n, k = map(int, input().split())
b = [0] * N
vis = [False] * N
num = 0 # 统计已经输出的全排列数量
def dfs(s):
global num
if s == n:
print(*b[:n])
num += 1 # 输出了一个排列,数量加1
return
if num == k: return # 只输出前k个
for i in range(1, n + 1):
if i == s + 1: continue # 处理错位:跳过a[i]=i不输出
if not vis[i]:
vis[i] = True
b[s] = i
dfs(s+1)
vis[i] = False
dfs(0) # 前n个数的全排列