2022 Shanghai Collegiate Programming Contest B
知识点:差分约束
Link:https://codeforces.com/gym/103931/problem/B。
被卡 SPFA 了呃呃。
一看出题人是这个人:
如何看待 SPFA 算法已死这种说法? - fstqwq 的回答 - 知乎,那没事了。
简述
给定参数 ,表示有一个长度为 的合法括号序列,且有 组限制。每组限制均为 的形式,表示括号序列区间 中,左括号数减去右括号数得到的差值为 。要求判断是否存在一个满足所有限制的合法括号序列,若存在则构造任意一组合法的解。
,,,。
2S,1024MB。
分析
给定限制为数量关系,考虑把括号序列合法的条件也抽象成数量关系。记一个左括号的权值为 1,右括号权值为 -1,记 表示序列的前缀 的权值之和,则一个长度为 的括号序列合法,则等价于:
- 。
- 。
- 。
则本题中给定的限制 可以表示为 的形式。对于一组满足限制的 的取值,通过差分我们可以唯一确定这组值对应的括号序列。问题变为是否存在一组 的取值满足上述所有限制。
考虑差分约束,记 表示到达节点 的最短路长度,将上述所有限制转化为三角不等式形式:
- ,考虑以节点 0 为起点,且向节点 连一条权值为 0 的边。
- ,即 ,从 向 0 连一条权值为 0 的边。
- 对于给定限制 ,即 ,从 向 连一条权值为 的边,从 向 连一条权值为 的边。
- ,可以发现如果 个限制均满足偶数长度的限制区间的 为偶数,奇数长度的限制区间的 为奇数,则 一定成立。则可考虑先判断给定限制是否符合上述条件,再将该条件放松为 ,从 向 连一条权值为 1 的边,从 向 连一条权值为 1 的边。
建图后运行差分约束算法,若存在负环则无解,否则根据 即可构造一组合法解。
此时如果直接使用 Bellman-Ford/SPFA 算法朴素地实现,由于边数的上界为 级别,则复杂度上界为 级别,在出题人的特别关照下,使用朴素的 SPFA 算法无法通过本题。
然而使用 SLF SPFA 可通过本题,且实际运行效率较高。std 则在此基础上采用了一种复杂度稳定的 算法,详见下文:
主要问题在于连边数量过多。不过我们可以发现有许多限制是冗余的,根据 ,对于三个限制 ,如果其中某两个限制合法,根据数量关系即可直接推出第三个限制是否合法;如果第三个限制合法,则它是冗余的。则我们可以在依次输入限制的同时,使用一个带权并查集辅助我们删去冗余限制。对于一个限制 ,我们将它抽象为一条带权边,加入该限制即为将并查集中 与 合并。显然,仅有将两个不同的集合合并起来的限制才是有效的,则真正有贡献的限制最多仅有 个。
记并查集中节点 所在集合的祖先为 ,记 表示区间 中左括号与右括号数量之差。一开始并查集中所有点都是孤立的,每输入一个限制,先判断 与 是否已经合并。如果已合并则判断 是否成立,若成立则该限制合法,为冗余限制,否则限制不合法;若未合并则将它们合并,维护过程详见代码。
边数变为 级别,则总复杂度变为 级别。
代码
正解:
复制复制//知识点:负环 /* By:Luckyblock */ // #pragma GCC optimize(2) #include <queue> #include <cctype> #include <cstdio> #include <cstring> #include <algorithm> #define LL long long const int kN = 3e3 + 10; const int kM = 5e6 + 10; //============================================================= int n, q; int fa[kN], val[kN]; int edgenum, head[kN], v[kM], w[kM], ne[kM]; int dis[kN], cnt[kN]; bool vis[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void AddEdge(int u_, int v_, int w_) { v[++ edgenum] = v_; w[edgenum] = w_; ne[edgenum] = head[u_]; head[u_] = edgenum; } bool SpfaBfs(int s_) { std::queue <int> q; memset(cnt, 0, sizeof (cnt)); memset(vis, 0, sizeof (vis)); memset(dis, 63, sizeof (dis)); q.push(s_); dis[s_] = 0; vis[s_] = true; while (! q.empty()) { int u_ = q.front(); q.pop(); vis[u_] = false; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i], w_ = w[i]; if (dis[u_] + w_ < dis[v_]) { dis[v_] = dis[u_] + w_; cnt[v_] = cnt[u_] + 1; if (cnt[v_] > n) return true; if (! vis[v_]) { q.push(v_); vis[v_] = true; } } } } return false; } int Find(int x_) { if (fa[x_] == x_) { val[x_] = 0; return x_; } int old_fa = fa[x_]; fa[x_] = Find(fa[x_]); val[x_] += val[old_fa]; return fa[x_]; } bool Merge(int x_, int y_, int c_) { int fx = Find(x_), fy = Find(y_); if (fx == fy) return ((val[x_] - val[y_]) == c_); AddEdge(x_, y_, c_), AddEdge(y_, x_, -c_); fa[fx] = fy; val[fx] = -val[x_] + c_ + val[y_]; return true; } //============================================================= int main() { // freopen("1.txt", "r", stdin); n = read(), q = read(); for (int i = 1; i <= n; ++ i) fa[i] = i; for (int i = 1; i <= q; ++ i) { int l_ = read(), r_ = read(), c_ = read(); if ((r_ - l_ + 1) % 2 != (abs(c_) % 2)) { printf("?\n"); return 0; } if (!Merge(l_ - 1, r_, c_)) { printf("?\n"); return 0; } } for (int i = 1; i <= n; ++ i) { AddEdge(i, 0, 0); AddEdge(i - 1, i, 1); //(i - 1) <= i + 1 AddEdge(i, i - 1, 1); //i <= (i - 1) + 1 } AddEdge(0, n, 0); if (SpfaBfs(0)) { printf("?\n"); return 0; } printf("! "); for (int i = 1; i <= n; ++ i) { // printf("%d ", dis[i]); if (dis[i] - dis[i - 1] == 1) { printf("("); } else { printf(")"); } } return 0; }
大力 SLF SPFA 爆炒:
//知识点:负环 /* By:Luckyblock */ // #pragma GCC optimize(2) #include <queue> #include <cctype> #include <cstdio> #include <cstring> #include <algorithm> #define LL long long const int kN = 3e3 + 10; const int kM = 5e6 + 10; //============================================================= int n, q; int e_num, head[kN], v[kM], w[kM], ne[kM]; int dis[kN], cnt[kN]; bool vis[kN]; //============================================================= inline int read() { int f = 1, w = 0; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1; for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); return f * w; } void Chkmax(int &fir_, int sec_) { if (sec_ > fir_) fir_ = sec_; } void Chkmin(int &fir_, int sec_) { if (sec_ < fir_) fir_ = sec_; } void AddEdge(int u_, int v_, int w_) { v[++ e_num] = v_; w[e_num] = w_; ne[e_num] = head[u_]; head[u_] = e_num; } void Init() { e_num = 0; memset(head, 0, sizeof (head)); } bool SpfaBfs(int s_) { std::deque <int> q; memset(cnt, 0, sizeof (cnt)); memset(vis, 0, sizeof (vis)); memset(dis, 63, sizeof (dis)); q.push_front(s_); dis[s_] = 0; vis[s_] = true; while (! q.empty()) { int u_ = q.front(); q.pop_front(); vis[u_] = false; for (int i = head[u_]; i; i = ne[i]) { int v_ = v[i], w_ = w[i]; if (dis[u_] + w_ < dis[v_]) { dis[v_] = dis[u_] + w_; cnt[v_] = cnt[u_] + 1; if (cnt[v_] > n) return true; if (! vis[v_]) { if (!q.empty() && dis[v_] > dis[q.front()]) q.push_back(v_); else q.push_front(v_); vis[v_] = true; } } } } return false; } //============================================================= int main() { // freopen("1.txt", "r", stdin); n = read(), q = read(); for (int i = 1; i <= q; ++ i) { int l_ = read(), r_ = read(), c_ = read(); if ((r_ - l_ + 1) % 2 != (abs(c_) % 2)) { printf("?\n"); return 0; } AddEdge(l_ - 1, r_, c_); AddEdge(r_, l_ - 1, -c_); } for (int i = 1; i <= n; ++ i) { AddEdge(i, 0, 0); AddEdge(i - 1, i, 1); //(i - 1) <= i + 1 AddEdge(i, i - 1, 1); //i <= (i - 1) + 1 } AddEdge(0, n, 0); if (SpfaBfs(0)) { printf("?\n"); return 0; } printf("! "); for (int i = 1; i <= n; ++ i) { // printf("%d ", dis[i]); if (dis[i] - dis[i - 1] == 1) { printf("("); } else { printf(")"); } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现