2022 Shanghai Collegiate Programming Contest B

知识点:差分约束

Link:https://codeforces.com/gym/103931/problem/B

被卡 SPFA 了呃呃。

一看出题人是这个人:
如何看待 SPFA 算法已死这种说法? - fstqwq 的回答 - 知乎,那没事了。

简述

给定参数 n,q,表示有一个长度为 n 的合法括号序列,且有 q 组限制。每组限制均为 li,ri,ci 的形式,表示括号序列区间 [li,ri] 中,左括号数减去右括号数得到的差值为 ci。要求判断是否存在一个满足所有限制的合法括号序列,若存在则构造任意一组合法的解。
2n30000qmin((n+12),5×105)1lirinncin
2S,1024MB。

分析

给定限制为数量关系,考虑把括号序列合法的条件也抽象成数量关系。记一个左括号的权值为 1,右括号权值为 -1,记 si 表示序列的前缀 [1,i] 的权值之和,则一个长度为 n 的括号序列合法,则等价于:

  • s0=sn=0
  • 1in, si0
  • 1in, |sisi1|=1

则本题中给定的限制 (li,ri,ci) 可以表示为 srsl1=ci 的形式。对于一组满足限制的 s 的取值,通过差分我们可以唯一确定这组值对应的括号序列。问题变为是否存在一组 s 的取值满足上述所有限制。

考虑差分约束,记 disi 表示到达节点 i 的最短路长度,将上述所有限制转化为三角不等式形式:

  • s0=sn=0,考虑以节点 0 为起点,且向节点 n 连一条权值为 0 的边。
  • 1in, si0,即 sis0,从 i 向 0 连一条权值为 0 的边。
  • 对于给定限制 srsl1=ci,即 cisrsl1ci,从 l1r 连一条权值为 ci 的边,从 rl1 连一条权值为 ci 的边。
  • 1in, |sisi1|=1,可以发现如果 q 个限制均满足偶数长度的限制区间的 ci 为偶数,奇数长度的限制区间的 ci 为奇数,则 sisi1 一定成立。则可考虑先判断给定限制是否符合上述条件,再将该条件放松为 1sisi11,从 i1i 连一条权值为 1 的边,从 ii1 连一条权值为 1 的边。

建图后运行差分约束算法,若存在负环则无解,否则根据 dis 即可构造一组合法解。

此时如果直接使用 Bellman-Ford/SPFA 算法朴素地实现,由于边数的上界为 O(q) 级别,则复杂度上界为 O(nq) 级别,在出题人的特别关照下,使用朴素的 SPFA 算法无法通过本题。

然而使用 SLF SPFA 可通过本题,且实际运行效率较高。std 则在此基础上采用了一种复杂度稳定的 O(n2) 算法,详见下文:


主要问题在于连边数量过多。不过我们可以发现有许多限制是冗余的,根据 srsl1=ci,对于三个限制 (i,j,c1),(i,k,c2),(j,k,c3),如果其中某两个限制合法,根据数量关系即可直接推出第三个限制是否合法;如果第三个限制合法,则它是冗余的。则我们可以在依次输入限制的同时,使用一个带权并查集辅助我们删去冗余限制。对于一个限制 (li,ri,ci),我们将它抽象为一条带权边,加入该限制即为将并查集中 li1ri 合并。显然,仅有将两个不同的集合合并起来的限制才是有效的,则真正有贡献的限制最多仅有 n 个。

记并查集中节点 i 所在集合的祖先为 fai,记 vali 表示区间 [i,fai] 中左括号与右括号数量之差。一开始并查集中所有点都是孤立的,每输入一个限制,先判断 li1ri 是否已经合并。如果已合并则判断 valli1valri=c 是否成立,若成立则该限制合法,为冗余限制,否则限制不合法;若未合并则将它们合并,维护过程详见代码。

边数变为 O(n) 级别,则总复杂度变为 O(n2) 级别。

代码

正解:

复制复制
//知识点:负环
/*
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;
}
posted @   Luckyblock  阅读(65)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示