预处理所有轮数,再 O(1) 回答询问
询问很多,不能每次重新模拟。因为 r <= 100,我们按轮数做 DP,每一轮扫描所有人的词库一次。
dp[t][x]:接龙 t 轮后,数字 x 能不能作为结尾,以及最后一轮可能由谁完成。
把“接龙轮数、结尾数字、上一轮选手”压成一个可以快速扫描的 DP。
询问很多,不能每次重新模拟。因为 r <= 100,我们按轮数做 DP,每一轮扫描所有人的词库一次。
这题最容易错在“不能连续选同一个人”。课堂上先讲 dp 的三种值,再用样例演示为什么一个 remain 就能标记后面一段结尾。
dp[t][x] 不是简单真假-1接龙 t 轮后,不能以数字 x 结尾。p可以以 x 结尾,但目前只知道最后一轮由第 p 个人完成。0可以以 x 结尾,而且最后一轮至少能由两个不同的人完成。扫描第 p 个人的词库时,当前位置数字是 x。如果上一轮能以 x 结尾,并且上一轮不一定非要由 p 完成,就可以从这里开启新一轮。
dp[t - 1][x] == 0 时最舒服,说明我们一定能换一个人接上。
演示使用题目样例:第 1 人 1 2 3 4 1,第 2 人 1 2 5,第 3 人 5 1 6,且 k = 3。
dp[t][x] = 0 表示什么?remain > 0,再判断当前位置能不能作为起点?| 轮数 | 样例中能看到的状态 | 说明 |
|---|---|---|
| 0 | dp[0][1] = 0 |
还没选人,所以任意人都能从数字 1 开始第一轮。 |
| 1 | dp[1][2] = 0,dp[1][3] = 1,dp[1][5] = 2,dp[1][6] = 3 |
数字 2 可以由第 1 人和第 2 人结尾,所以状态变成 0。 |
| 2 | dp[2][4] = 1 |
从第一轮结尾 2 出发,第 1 人可以选择 2 3 4,所以询问 (2,4) 输出 1。 |
| 3 | dp[3][4] = -1 |
要在第 1 人序列里到 4,能用的前驱 3 只由第 1 人结尾,不能连续再选第 1 人。 |
读代码重点:markReachable 维护三种状态,remain 负责把后面一段位置标为可达。
#include <bits/stdc++.h>
using namespace std;
const int MAXC = 200000;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while (T--) {
int n, k, q;
cin >> n >> k >> q;
vector<vector<int>> s(n + 1);
for (int i = 1; i <= n; i++) {
int len;
cin >> len;
s[i].resize(len);
for (int j = 0; j < len; j++) {
cin >> s[i][j];
}
}
vector<pair<int, int>> query(q);
int maxR = 0;
for (int i = 0; i < q; i++) {
int r, c;
cin >> r >> c;
query[i] = {r, c};
maxR = max(maxR, r);
}
vector<vector<int>> dp(maxR + 1, vector<int>(MAXC + 1, -1));
dp[0][1] = 0;
auto markReachable = [&](int round, int value, int person) {
int &state = dp[round][value];
if (state == -1) {
state = person;
} else if (state != person) {
state = 0; // 至少两个不同的人都能作为最后一轮选手
}
};
for (int round = 1; round <= maxR; round++) {
for (int person = 1; person <= n; person++) {
int remain = 0;
for (int x : s[person]) {
if (remain > 0) {
markReachable(round, x, person);
remain--;
}
// 当前 x 能作为本轮起点,后面 k - 1 个位置能作为本轮结尾。
if (dp[round - 1][x] != -1 && dp[round - 1][x] != person) {
remain = k - 1;
}
}
}
}
for (auto item : query) {
int r = item.first;
int c = item.second;
cout << (dp[r][c] != -1 ? 1 : 0) << '\n';
}
}
return 0;
}
dp[0][1] = 0 表示第一轮可以从任意人的数字 1 开始。state = 0 不是清零,而是表示“已经有多个不同的人可达”。remain = k - 1 只覆盖后面的数字,所以不会产生长度为 1 的接龙序列。只知道某个数字可达,不知道最后由谁接,无法判断下一轮能不能换人。
题目要求长度至少为 2。当前数字只能影响它后面的位置。
q 最大是 105。正确做法是先预处理所有轮数,再直接回答。