1 条题解
-
0
1967D - 漫长的非递减之路
一、题意(极简回顾)
- 每次操作:选任意位置 ,令
- 目标:用最少操作次数让数组变成 非递减
- 无解输出
二、标程核心思路
1. 为什么二分答案?
单独模拟每一步操作无法优化,因此:
- 二分操作次数
- 判断: 次操作内能否把数组变成非递减
2. 判断函数:贪心构造(最优策略)
从左到右构造数组:
- 让 尽可能小
- 满足
- 满足 是初始值 步内能到达的值
能构造完 可行。
3. 图论模型
每个数字 连边: 图是 内向基环森林(环 + 挂在环上的树)
4. 可达性判定(标程核心)
判断 能否 步变成 :
- 树上: 在 子树内,且深度差
- 经过环:满足步数公式 + 子树包含
三、完整标程代码(逐行超详细注释)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXM = 1e6 + 5; const int MAXN = 1e6 + 5; int n, m; int a[MAXN], b[MAXM]; // 基环树存储 vector<int> G[MAXM]; // 访问标记 / 深度 / dfs序 / 子树大小 int vis[MAXM], dep[MAXM], dfn[MAXM], sz[MAXM]; // 根节点 / 环上割边 s->t int root[MAXM], cir_s[MAXM], cir_t[MAXM]; int dfn_idx; // 环相关 int in_cycle[MAXM], cycle_id[MAXM]; vector<int> cycle[MAXM]; int tot_cycle; // ---------------------- 1. 找基环树中的环 ---------------------- int dfs_cycle(int u) { vis[u] = 1; for (int v : G[u]) { if (vis[v] == 1) { // 找到环 tot_cycle++; int cur = u; while (cur != v) { in_cycle[cur] = 1; cycle[tot_cycle].push_back(cur); cur = b[cur]; } in_cycle[v] = 1; cycle[tot_cycle].push_back(v); return v; } if (!vis[v]) { int res = dfs_cycle(v); if (res > 0) { if (in_cycle[u]) return -1; in_cycle[u] = 1; cycle[tot_cycle].push_back(u); return (res == u) ? -1 : res; } } } vis[u] = 2; return -1; } // ---------------------- 2. 处理树结构,预处理 dep, dfn, sz ---------------------- void dfs_tree(int u, int rt, int s, int t) { dfn[u] = ++dfn_idx; sz[u] = 1; root[u] = rt; cir_s[u] = s; cir_t[u] = t; for (int v : G[u]) { if (!in_cycle[v]) { // 只处理树边 dep[v] = dep[u] + 1; dfs_tree(v, rt, s, t); sz[u] += sz[v]; } } } // ---------------------- 3. 初始化整张基环树 ---------------------- void build() { // 清空数据 for (int i = 1; i <= m; i++) { G[i].clear(); vis[i] = in_cycle[i] = cycle_id[i] = 0; dep[i] = dfn[i] = sz[i] = 0; } for (int i = 1; i <= tot_cycle; i++) cycle[i].clear(); dfn_idx = tot_cycle = 0; // 建图 x -> b[x] for (int i = 1; i <= m; i++) G[b[i]].push_back(i); // 找所有环 for (int i = 1; i <= m; i++) if (!vis[i]) dfs_cycle(i); // 处理每个环上的树 for (int id = 1; id <= tot_cycle; id++) { auto& vec = cycle[id]; int len = vec.size(); for (int i = 0; i < len; i++) { int s = vec[i], t = vec[(i+1)%len]; dep[s] = 0; dfs_tree(s, s, s, t); } } } // ---------------------- 4. O(1) 判断 v 能否 k 步到达 u ---------------------- bool reachable(int u, int v, int k) { if (u == v) return true; if (root[u] != root[v]) return false; // 情况1:在同一棵树上,v是祖先,u是后代 if (dfn[v] <= dfn[u] && dfn[u] <= dfn[v] + sz[v] - 1) return (dep[u] - dep[v]) <= k; // 情况2:需要经过环 int s = cir_s[v], t = cir_t[v]; // v 能到 s,t 能到 u if (!(dfn[v] <= dfn[s] && dfn[s] <= dfn[v] + sz[v] - 1)) return false; if (!(dfn[t] <= dfn[u] && dfn[u] <= dfn[t] + sz[t] - 1)) return false; // 总步数:v->s + 1(环边) + t->u int step = (dep[s] - dep[v]) + (dep[u] - dep[t]) + 1; return step <= k; } // ---------------------- 5. 二分check:k次操作能否构造合法数组 ---------------------- bool check(int k) { int last = 0; for (int i = 1; i <= n; i++) { int st = a[i]; int best = -1; // 贪心选最小的 >= last 且可达的值 for (int w = max(last, 1); w <= m; w++) { if (reachable(w, st, k)) { best = w; break; } } if (best == -1) return false; last = best; } return true; } // ---------------------- 主函数 ---------------------- void solve() { cin >> n >> m; for (int i = 1; i <= n; i++) cin >> a[i]; for (int i = 1; i <= m; i++) cin >> b[i]; build(); // 二分答案:0~30 足够(操作次数极少) int l = 0, r = 30, ans = -1; while (l <= r) { int mid = (l + r) / 2; if (check(mid)) { ans = mid; r = mid - 1; } else l = mid + 1; } cout << ans << '\n'; } int main() { ios::sync_with_stdio(false); cin.tie(0); int t; cin >> t; while (t--) solve(); return 0; }
四、代码结构完全对应标程
代码模块 标程对应内容 dfs_cycle找基环树的环 dfs_tree求深度、dfn、子树大小 reachable判断 步可达 check贪心构造非递减数组 二分 求最小操作次数
五、复杂度分析(100% 符合题目限制)
- 建图、找环、DFS:
- 二分:
- 每次 check:
- 总复杂度: 轻松通过 数据
- 1
信息
- ID
- 7068
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者