2024 接龙 · 方案二 滑动窗口逐轮推进
每人每轮只扫一遍词库,窗口里数可当起点的位置
正文
// CSP-J 2024 复赛 T4 · 接龙(方案二 · 满分)
// 原题:https://oj.yecheng.tv/p/ccf-CSPJ2024D
//
// 方案二 · 逐轮推进 + 滑动窗口算「哪些位置能当本轮结尾」
// 方案一慢在两层枚举(人 × 长度)。换个角度:一轮一轮往前推,
// 只维护「第 r 轮能以元素 v 结尾」这件事,并为每个 v 记下两个不同的人编号
// (记两个就够:要避开上一轮那个人,两个里总有一个能用)。
// 推进一轮时,对每个人 p 单独扫一遍他的词库:
// 位置 j 能当起点,当且仅当 第 r-1 轮能以 S_p[j] 结尾,且那一轮里有人不是 p;
// 位置 q 能当本轮结尾,当且仅当窗口 [q-k+1, q-1] 里存在能当起点的位置。
// 后者用滑动窗口计数:q 往右挪一格时,把 j = q-1 加进来、把 j = q-k 划出去。
// 这样每个人每轮只扫一遍词库,总复杂度 O(轮数 * 词库总长),
// r 最多 100、词库总长 2e5,完全来得及。
// 任务按 r 分好组,算完第 r 轮就把这一组的答案填进 ans 数组,最后按原顺序输出。
#include <bits/stdc++.h>
using namespace std;
const int MAXV = 200005;
vector<vector<int> > S;
int maxR;
int k;
bool curOk[MAXV]; // 上一轮:能不能以这个值结尾
int who1[MAXV], who2[MAXV]; // 上一轮做到这件事的两个不同人编号
bool nxtOk[MAXV];
int nw1[MAXV], nw2[MAXV];
// 第 r 轮时,第 p 个人的位置 j 能不能当接龙起点
bool canStart(int p, int j, int r) {
int v = S[p][j];
if (r == 1) return v == 1; // 第一轮必须以 1 开头
if (!curOk[v]) return false;
if (who1[v] != p) return true; // 有一个做到的人不是 p
if (who2[v] != -1 && who2[v] != p) return true;
return false;
}
int main() {
freopen("chain.in", "r", stdin);
freopen("chain.out", "w", stdout);
int T;
cin >> T;
while (T--) {
int n, q;
cin >> n >> k >> q;
S.assign(n + 1, vector<int>(1, 0)); // 下标从 1 开始
for (int i = 1; i <= n; i++) {
int l;
cin >> l;
for (int j = 0; j < l; j++) {
int x;
cin >> x;
S[i].push_back(x);
}
}
vector<pair<int, int> > ask(q);
maxR = 0;
for (int i = 0; i < q; i++) {
cin >> ask[i].first >> ask[i].second;
maxR = max(maxR, ask[i].first);
}
vector<vector<int> > byRound(maxR + 1);
for (int i = 0; i < q; i++) byRound[ask[i].first].push_back(i);
vector<int> answer(q, 0);
memset(curOk, 0, sizeof(curOk));
for (int r = 1; r <= maxR; r++) {
memset(nxtOk, 0, sizeof(nxtOk));
for (int v = 0; v < MAXV; v++) {
nw1[v] = -1;
nw2[v] = -1;
}
for (int p = 1; p <= n; p++) {
int cnt = 0; // 窗口内可当起点的位置个数
int l = (int)S[p].size() - 1;
for (int qpos = 1; qpos <= l; qpos++) {
if (qpos >= 2 && canStart(p, qpos - 1, r)) cnt++;
if (qpos - k >= 1 && canStart(p, qpos - k, r)) cnt--;
if (cnt > 0) {
int v = S[p][qpos];
nxtOk[v] = true;
if (nw1[v] == -1) nw1[v] = p;
else if (nw1[v] != p && nw2[v] == -1) nw2[v] = p;
}
}
}
memcpy(curOk, nxtOk, sizeof(curOk));
memcpy(who1, nw1, sizeof(who1));
memcpy(who2, nw2, sizeof(who2));
for (int t = 0; t < (int)byRound[r].size(); t++) {
int idx = byRound[r][t];
answer[idx] = curOk[ask[idx].second] ? 1 : 0;
}
}
for (int i = 0; i < q; i++) cout << answer[i] << "\n";
}
return 0;
}
CSP-J 标程 · 复赛真题的其它内容
- 2019 数字游戏 · 方案一 逐字符统计
- 2019 数字游戏 · 方案二 二进制位运算
- 2020 优秀的拆分 · 方案一 贪心从大到小减
- 2020 优秀的拆分 · 方案二 直接看二进制位
- 2021 分糖果 · 方案一 枚举每个 k
- 2021 分糖果 · 方案二 看余数在哪一段
- 2022 乘方 · 方案一 边乘边判断
- 2022 乘方 · 方案二 快速幂加封顶
- 2023 小苹果 · 方案一 照规则真模拟
- 2023 小苹果 · 方案二 只盯住两个数字
- 2024 扑克牌 · 方案一 用集合去重
- 2024 扑克牌 · 方案二 二维布尔表
- 2025 拼数 · 方案一 收集后降序排序
- 2025 拼数 · 方案二 桶计数
- 2019 公交换乘 · 方案一 暴力匹配
- 2019 公交换乘 · 方案二 时间窗口优化
- 2020 直播获奖 · 方案一 每轮排序
- 2020 直播获奖 · 方案二 桶计数
- 2021 插入排序 · 方案一 每次真排一遍
- 2021 插入排序 · 方案二 增量维护有序表
- 2022 解密 · 方案一 二分求整数平方根
- 2022 解密 · 方案二 先估后校开方
- 2023 公路 · 方案一 朴素贪心
- 2023 公路 · 方案二 单调栈预处理
- 2024 地图探险 · 方案一 四方向分支写
- 2024 地图探险 · 方案二 方向数组
- 2025 座位 · 方案一 把座位表填出来
- 2025 座位 · 方案二 直接算排名
- 2019 纪念品 · 方案一 逐天完全背包
- 2019 纪念品 · 方案二 砍掉不赚钱物品
- 2020 表达式 · 方案一 每次重算后缀式
- 2020 表达式 · 方案二 建树加关键性传播
- 2021 网络连接 · 方案一 手写解析
- 2021 网络连接 · 方案二 读入后回拼校验
- 2022 逻辑表达式 · 方案一 递归分治
- 2022 逻辑表达式 · 方案二 递归下降
- 2023 一元二次方程 · 方案一 照公式逐步写
- 2023 一元二次方程 · 方案二 拆成小函数
- 2024 小木棍 · 方案一 先定位数再贪心
- 2024 小木棍 · 方案二 预处理最小数字表
- 2025 异或和 · 方案一 贪心能接就接
- 2025 异或和 · 方案二 动态规划加值域数组
- 2019 加工零件 · 方案一 递归加记忆化
- 2019 加工零件 · 方案二 奇偶最短路
- 2020 方格取数 · 方案一 深搜回溯
- 2020 方格取数 · 方案二 按列动态规划
- 2021 小熊的果篮 · 方案一 每轮扫一遍
- 2021 小熊的果篮 · 方案二 链表加有序集合
- 2022 上升点列 · 方案一 先只考虑不加点
- 2022 上升点列 · 方案二 二维动态规划
- 2023 旅游巴士 · 方案一 分层图加优先队列
- 2023 旅游巴士 · 方案二 状态压成一维
- 2024 接龙 · 方案一 按定义广搜
- 2025 多边形 · 方案一 枚举所有子集
- 2025 多边形 · 方案二 排序加计数 DP