第几条明暗交错子序列(C++/Py/Java /Js/Go)题解
京东 2026年8月29号 笔试真题 第一题
题目内容
灯带车间要把一条只有暗格和亮格的样带拆成明暗交错的子序列,再按字典规矩挑出指定名次的那一条,写入当晚的工艺单。
样带是长度为 mmm 的 010101 串 b=b1b2...bmb=b_1b_2\dots b_mb=b1b2...bm,其中 0 表示暗格,1 表示亮格。值班员会选出一组下标 1≤p1<p2<⋯<pu≤m1 \le p_1 < p_2 < \dots < p_u \le m1≤p1<p2<⋯<pu≤m(uuu 可以为 0),按顺序拼成子序列 g=bp1bp2...bpug=b_{p_1}b_{p_2}\dots b_{p_u}g=bp1bp2...bpu。
如果 ggg 里没有相邻两格同暗或同亮,也就是对任意 1≤i<∣g∣1 \le i < |g|1≤i<∣g∣ 都有 gi≠gi+1g_i \neq g_{i+1}gi=gi+1,就称 ggg 是一条明暗交错带。
所有能得到明暗交错带的下标集合都算不同方案,即使拼出来的串相同也各记一次。把这些 ggg 收成一个允许重复的序列,先按长度从短到长,长度相同时再按字典序(0 小于 1)排列。空串也参与排列。
请找出排列后的第 qqq 条;如果没有第 qqq 条,输出 -1。输入保证第 qqq 条不会是空串。
询问组数与样带长度满足 1≤m≤1 \le m \le1≤m≤ 2000,2≤q≤2 \le q \le2≤q≤ 1000000000000000。
输入描述
第一行两个整数 mmm、qqq(1≤m≤1 \le m \le1≤m≤ 2000,2≤q≤2 \le q \le2≤q≤ 1000000000000000),表示样带长度和要取的名次。
第二行一个长度为 mmm 的 010101 字符串 bbb。
输出描述
输出一行,即排列后的第 qqq 条明暗交错带;若不存在,输出 -1。
样例1
输入
3 4
110
输出
1
说明
合法方案对应的串为:空串、两处单独的 1、一处 0、以及两条 10。按长度再字典序后是空串、0、1、1、10、10。第 4 条是 1。
样例2
输入
4 2
0101
输出
0
说明
空串最短,下一名是长度为 1 且字典序更小的 0。q=2q=2q=2,因此输出 0。
样例3
输入
1 5
0
输出
-1
说明
只有空串和 0 两条,没有第 5 条。
题解和思路
思路
实现思路:动态规划
- 虽然下标方案很多,但对于固定长度,合法的 01 交错串只有两种,(0开头或者1开头)。因此不需要枚举字符串,只需要统计
每一种交错字符串作为子序列出现了多少次 - 定义dp数组,其中
dp[len][x] x ∈[0,1],表示当前已经扫描的字符串中,长度为 len、最后一个字符是 0 的交错子序列数量。从前往后进行扫描时,扫描到第i个字符时对应要转移的状态为:dp[1][x] += 1对应字符长度为1数量 + 1dp[y][x] += dp[len - 1][x ^ 1], 其中1 < y <= m
- 根据2求出每个状态数量时,减去空串
q = q - 1, 按照长度从小到大,长度相同按照0开头 到 1开头确定所处目标字符串即可。如果各种状态数量总和小于q则输出-1. - 算法整体时间复杂度为
O(m ^ 2)
C++
cpp
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m;
ll q;
cin >> m >> q;
string b;
cin >> b;
// dp[len][c]:
// 当前扫描到的位置为止,长度为 len、最后一个字符为 c,的交错子序列数量
vector<vector<ll>> dp(m + 1, vector<ll>(2, 0));
// 超过 q 后就没有继续精确计算的必要
auto addCap = [&](ll &a, ll b) {
if (a >= q) return;
if (b >= q - a) a = q;
else a += b;
};
for (char ch : b) {
int c = ch - '0';
// 必须倒序,避免当前字符被重复使用
for (int len = m; len >= 2; --len) {
addCap(dp[len][c], dp[len - 1][1 - c]);
}
// 单独当前字符构成长度 1 的交错子序列
addCap(dp[1][c], 1);
}
// 去掉空串
q = q - 1;
// 按长度从小到大寻找第 q 个
for (int len = 1; len <= m; ++len) {
ll count0, count1;
if (len & 1) {
// 奇数长度:首尾相同
count0 = dp[len][0]; // 0101...
count1 = dp[len][1]; // 1010...
} else {
// 偶数长度:首尾不同
count0 = dp[len][1]; // 0101...
count1 = dp[len][0]; // 1010...
}
// 字典序:0 开头的更小
if (q <= count0) {
string ans;
ans.reserve(len);
for (int i = 0; i < len; ++i) {
ans += char('0' + (i & 1));
}
cout << ans << '\n';
return 0;
}
q -= count0;
if (q <= count1) {
string ans;
ans.reserve(len);
for (int i = 0; i < len; ++i) {
ans += char('1' - (i & 1));
}
cout << ans << '\n';
return 0;
}
q -= count1;
}
cout << -1 << '\n';
return 0;
}
Java
java
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int m = Integer.parseInt(st.nextToken());
long q = Long.parseLong(st.nextToken());
String b = br.readLine();
// dp[len][c]:
// 当前扫描到的位置为止,长度为 len、最后一个字符为 c,的交错子序列数量
long[][] dp = new long[m + 1][2];
// 超过 q 后就没有继续精确计算的必要
for (int i = 0; i < m; i++) {
int c = b.charAt(i) - '0';
// 必须倒序,避免当前字符被重复使用
for (int len = m; len >= 2; len--) {
if (dp[len][c] >= q) continue;
long add = dp[len - 1][1 - c];
if (add >= q - dp[len][c]) {
dp[len][c] = q;
} else {
dp[len][c] += add;
}
}
// 单独当前字符构成长度 1 的交错子序列
if (dp[1][c] < q) {
if (1 >= q - dp[1][c]) {
dp[1][c] = q;
} else {
dp[1][c]++;
}
}
}
// dp 中没有统计空串,因此这里 q = q - 1
q -= 1;
// 按长度从小到大寻找第 q 个
for (int len = 1; len <= m; len++) {
long count0, count1;
if ((len & 1) == 1) {
// 奇数长度:首尾相同
count0 = dp[len][0]; // 0101...
count1 = dp[len][1]; // 1010...
} else {
// 偶数长度:首尾不同
count0 = dp[len][1]; // 0101...
count1 = dp[len][0]; // 1010...
}
// 字典序:0 开头的更小
if (q <= count0) {
StringBuilder ans = new StringBuilder();
for (int i = 0; i < len; i++) {
ans.append((char) ('0' + (i & 1)));
}
System.out.println(ans);
return;
}
q -= count0;
if (q <= count1) {
StringBuilder ans = new StringBuilder();
for (int i = 0; i < len; i++) {
ans.append((char) ('1' - (i & 1)));
}
System.out.println(ans);
return;
}
q -= count1;
}
System.out.println(-1);
}
}
python
python
import sys
input = sys.stdin.readline
m, q = map(int, input().split())
b = input().strip()
# dp[len][c]:
# 当前扫描到的位置为止,长度为 len、最后一个字符为 c,的交错子序列数量
dp = [[0, 0] for _ in range(m + 1)]
# 超过 q 后就没有继续精确计算的必要
def add_cap(a, b):
if a >= q:
return q
if b >= q - a:
return q
return a + b
for ch in b:
c = int(ch)
# 必须倒序,避免当前字符被重复使用
for length in range(m, 1, -1):
dp[length][c] = add_cap(dp[length][c], dp[length - 1][1 - c])
# 单独当前字符构成长度 1 的交错子序列
dp[1][c] = add_cap(dp[1][c], 1)
# dp 中没有统计空串,因此需要
q = q - 1
# 按长度从小到大寻找第 q 个
for length in range(1, m + 1):
if length & 1:
# 奇数长度:首尾相同
count0 = dp[length][0] # 0101...
count1 = dp[length][1] # 1010...
else:
# 偶数长度:首尾不同
count0 = dp[length][1] # 0101...
count1 = dp[length][0] # 1010...
# 字典序:0 开头的更小
if q <= count0:
ans = ''.join(str(i & 1) for i in range(length))
print(ans)
sys.exit()
q -= count0
if q <= count1:
ans = ''.join(str(1 - (i & 1)) for i in range(length))
print(ans)
sys.exit()
q -= count1
print(-1)
Javascript
js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const lines = [];
rl.on('line', line => {
lines.push(line.trim());
});
rl.on('close', () => {
const [mStr, qStr] = lines[0].split(/\s+/);
const m = Number(mStr);
let q = BigInt(qStr);
const b = lines[1];
// dp[len][c]:
// 当前扫描到的位置为止,长度为 len、最后一个字符为 c,的交错子序列数量
const dp = Array.from({ length: m + 1 }, () => [0n, 0n]);
// 超过 q 后就没有继续精确计算的必要
function addCap(a, b) {
if (a >= q) return q;
if (b >= q - a) {
return q;
}
return a + b;
}
for (const ch of b) {
const c = Number(ch);
// 必须倒序,避免当前字符被重复使用
for (let len = m; len >= 2; len--) {
dp[len][c] = addCap(
dp[len][c],
dp[len - 1][1 - c]
);
}
// 单独当前字符构成长度 1 的交错子序列
dp[1][c] = addCap(dp[1][c], 1n);
}
// dp 中没有统计空串,因此需要
q -= 1n;
// 按长度从小到大寻找第 q 个
for (let len = 1; len <= m; len++) {
let count0, count1;
if (len & 1) {
// 奇数长度:首尾相同
count0 = dp[len][0]; // 0101...
count1 = dp[len][1]; // 1010...
} else {
// 偶数长度:首尾不同
count0 = dp[len][1]; // 0101...
count1 = dp[len][0]; // 1010...
}
// 字典序:0 开头的更小
if (q <= count0) {
let ans = '';
for (let i = 0; i < len; i++) {
ans += String(i & 1);
}
console.log(ans);
return;
}
q -= count0;
if (q <= count1) {
let ans = '';
for (let i = 0; i < len; i++) {
ans += String(1 - (i & 1));
}
console.log(ans);
return;
}
q -= count1;
}
console.log(-1);
});
Go
go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
out := bufio.NewWriter(os.Stdout)
defer out.Flush()
var m int
var q int64
fmt.Fscan(in, &m, &q)
var b string
fmt.Fscan(in, &b)
// dp[len][c]:
// 当前扫描到的位置为止,长度为 len、最后一个字符为 c,的交错子序列数量
dp := make([][2]int64, m+1)
// 超过 q 后就没有继续精确计算的必要
addCap := func(a *int64, b int64) {
if *a >= q {
return
}
if b >= q-*a {
*a = q
} else {
*a += b
}
}
for _, ch := range b {
c := int(ch - '0')
// 必须倒序,避免当前字符被重复使用
for length := m; length >= 2; length-- {
addCap(&dp[length][c], dp[length-1][1-c])
}
// 单独当前字符构成长度 1 的交错子序列
addCap(&dp[1][c], 1)
}
// dp 中没有统计空串,因此需要
q = q - 1
// 按长度从小到大寻找第 q 个
for length := 1; length <= m; length++ {
var count0, count1 int64
if length&1 == 1 {
// 奇数长度:首尾相同
count0 = dp[length][0] // 0101...
count1 = dp[length][1] // 1010...
} else {
// 偶数长度:首尾不同
count0 = dp[length][1] // 0101...
count1 = dp[length][0] // 1010...
}
// 字典序:0 开头的更小
if q <= count0 {
ans := make([]byte, length)
for i := 0; i < length; i++ {
ans[i] = byte('0' + byte(i&1))
}
fmt.Fprintln(out, string(ans))
return
}
q -= count0
if q <= count1 {
ans := make([]byte, length)
for i := 0; i < length; i++ {
ans[i] = byte('1' - byte(i&1))
}
fmt.Fprintln(out, string(ans))
return
}
q -= count1
}
fmt.Fprintln(out, -1)
}