DS 优化建图

题单来一个(不是我组的)。

CF786B Legacy

板子题先来一个:\(u \to v, u \to [l, r], [l, r] \to v\) 连边,求最短路。

考虑将区间 \([l, r]\) 用线段树的方式拆成 \(\log n\) 个小区间,然后将 \(u\) 分别连向这些区间即可。

然后你会发现线段树的父节点和儿子节点是可到达的,所以也要连边。

由于有 \(u \to [l, r], [l, r] \to v\),连边的方向相反,所以要在两棵线段树上完成,且两棵树的连边方向不一样(一个是从根往下,另一个是从叶子往上)。

还有一点就是说两棵树的叶子其实代表一个点,所以也要互相连边权为 \(0\) 的边。

注意:一定要算清楚一共有多少个点!!!

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define PII pair<int, ll>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define ll long long
using namespace std;
const int N = 1e6 + 5; const ll INF = (ll)1e18 + 5;
int n, Q, S, vis[N]; vector<PII > G[N]; vector<int> node; ll dis[N];
struct Node {
	int id; ll val;
	inline bool operator < (const Node & t) const { return val > t.val; }
}; priority_queue<Node> q;
struct Segment_Tree {
	#define lc (p << 1)
	#define rc (p << 1 | 1)
	#define mid ((tr[p].l + tr[p].r) >> 1)
	int x; struct Tree { int l, r; } tr[N << 2];
	inline bool In(int p, int l, int r) { return l <= tr[p].l && tr[p].r <= r; }
	void build(int p, int l, int r) {
		tr[p].l = l, tr[p].r = r;
		if (p > 1) {
			if (x == n)  G[(p >> 1) + x].pb(mp(p + x, 0));
			else  G[p + x].pb(mp((p >> 1) + x, 0));
		} if (l == r)  G[p + x].pb(mp(l, 0)), G[l].pb(mp(p + x, 0));
		else  build(lc, l, mid), build(rc, mid + 1, r);
	} void query(int p, int l, int r) {
		if (In(p, l, r))  return node.push_back(x + p);
		if (l <= mid)  query(lc, l, r);
		if (r > mid)  query(rc, l, r);
	}
	#undef lc
	#undef rc
	#undef mid
} SGT[2];
inline void Dijkstra() {
	_for (i, 1, n * 9)  dis[i] = INF, vis[i] = 0; q.push((Node){S, 0}), dis[S] = 0;
	while (q.size()) {
		int u = q.top().id; q.pop();
		if (vis[u])  continue; vis[u] = 1;
		for (PII v : G[u])  if (dis[v.fi] > dis[u] + v.se)  dis[v.fi] = dis[u] + v.se, q.push((Node){v.fi, dis[v.fi]});
	}
} int main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> Q >> S, SGT[0].x = n, SGT[1].x = n * 5, SGT[0].build(1, 1, n), SGT[1].build(1, 1, n); int op, x, y, z, w;
	while (Q -- ) {
		cin >> op >> x >> y >> z;
		if (op == 1)  G[x].pb(mp(y, z));
		else if (op == 2) {
			cin >> w, node.clear(), SGT[0].query(1, y, z);
			for (int u : node)  G[x].pb(mp(u, w));
		} else if (op == 3) {
			cin >> w, node.clear(), SGT[1].query(1, y, z);
			for (int u : node)  G[u].pb(mp(x, w));
		}
	} Dijkstra();
	_for (i, 1, n)  cout << (dis[i] == INF ? - 1 : dis[i]) << " "; cout << "\n";
	return 0;
}

P6348 [PA 2011] Journeys

再来一个板子:\([l_1, r_1] \to [l_2, r_2]\) 连边,求最短路。

有两种连边方式:

  • 暴力建边:分别拆成 \(\log n\) 个区间,总共 \(\log^2 n\) 条边。

  • 建虚点:\([l_1, r_1] \to A \to B \to [l_2, r_2]\),共 \(2 \log n\) 条边。

显然后者更优。

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define PII pair<int, ll>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define ll long long
using namespace std;
const int N = 1e7 + 5, INF = 1e9 + 5;
int n, Q, S, vis[N], dis[N]; vector<PII > G[N]; vector<int> node1, node2;
struct Node {
	int id; ll val;
	inline bool operator < (const Node & t) const { return val > t.val; }
}; priority_queue<Node> q;
struct Segment_Tree {
	#define lc (p << 1)
	#define rc (p << 1 | 1)
	#define mid ((tr[p].l + tr[p].r) >> 1)
	int x; struct Tree { int l, r; } tr[N << 2];
	inline bool In(int p, int l, int r) { return l <= tr[p].l && tr[p].r <= r; }
	void build(int p, int l, int r) {
		tr[p].l = l, tr[p].r = r;
		if (p > 1) {
			if (x == n + 4 * Q)  G[p + x].pb(mp(p / 2 + x, 0));
			else  G[p / 2 + x].pb(mp(p + x, 0));
		} if (l == r)  G[p + x].pb(mp(l + Q * 4, 0)), G[l + Q * 4].pb(mp(p + x, 0));
		else  build(lc, l, mid), build(rc, mid + 1, r);
	} void query(int p, int l, int r, vector<int> & node) {
		if (In(p, l, r))  return node.push_back(x + p);
		if (l <= mid)  query(lc, l, r, node);
		if (r > mid)  query(rc, l, r, node);
	}
	#undef lc
	#undef rc
	#undef mid
} SGT[2];
inline void Dijkstra() {
	_for (i, 1, n * 9 + 4 * Q)  dis[i] = INF, vis[i] = 0; q.push((Node){S, 0}), dis[S] = 0;
	while (q.size()) {
		int u = q.top().id; q.pop();
		if (vis[u])  continue; vis[u] = 1;
		for (PII v : G[u])  if (dis[v.fi] > dis[u] + v.se)  dis[v.fi] = dis[u] + v.se, q.push((Node){v.fi, dis[v.fi]});
	}
} int main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> Q >> S, S += 4 * Q, SGT[0].x = n + Q * 4, SGT[1].x = n * 5 + Q * 4, SGT[0].build(1, 1, n), SGT[1].build(1, 1, n); int x, y, z, w, u = - 1, v = 0;
	_for (_, 1, Q) {
		cin >> w >> x >> y >> z, node1.clear(), SGT[0].query(1, w, x, node1), node2.clear(), SGT[1].query(1, y, z, node2), u += 2, v += 2;
		for (int i : node1)  G[i].pb(mp(u, 0)); G[u].pb(mp(v, 1));
		for (int i : node2)  G[v].pb(mp(i, 0)); node1.clear(), SGT[0].query(1, y, z, node1), node2.clear(), SGT[1].query(1, w, x, node2), u += 2, v += 2;
		for (int i : node1)  G[i].pb(mp(u, 0)); G[u].pb(mp(v, 1));
		for (int i : node2)  G[v].pb(mp(i, 0));
	} Dijkstra();
	_for (i, 1 + 4 * Q, n + 4 * Q)  cout << dis[i] << "\n";
	return 0;
}

P5344 【XR-1】逛森林

第一想法是树剖 + 线段树优化建图,但你注意到会有 \(\ge m \log^2 n\) 个点,空间和时间都爆炸了。

具体的,设 \(UP_{u, i}\) 表示点 \(u\) 向上跳 \(2^i\) 步到达的点。那么我们可以把一个点拆为入点和出点,连接 \(UP_{u, i}\)\(UP_{u, i - 1}\) 的入点和出点即可。

这样的点数是多少呢?首先 \(UP_{u, i}\)\(n \log n\) 个点,每个点都拆为入点和出点。对于一条边都建两个虚点。所以总点数 \(n + 2 n \log n + 2m\),边数 \(\le 2 n \log n + 5 m \log n\),优于树剖建图。

放一张图(贺的洛谷题解):

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define _all(i, a, b)  for (int i = (a); i >= (b); i -- )
#define PII pair<int, int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
const int N = 5e4 + 5, M = 1e6 + 5, V = 16, K = N + 2 * N * V + 2 * M, INF = 1e9 + 5;
int n, Q, L, qcnt, S, ncnt, vis[K], dep[N], UP[N][V], in[N][V], out[N][V], dis[K]; vector<PII > G[K]; vector<int> Tr[N];
struct OP2 { int u1, v1, u2, v2, w; } tmp[M];
struct DSU {
	int fa[N];
	inline void init() { iota(fa + 1, fa + n + 1, 1); }
	int find(int u) { return fa[u] == u ? u : fa[u] = find(fa[u]); }
	inline bool con(int u, int v) { return find(u) == find(v); }
	inline void merge(int u, int v) { fa[find(u)] = find(v); }
} DSU;
struct Node {
	int id, val;
	inline bool operator < (const Node & t) const { return val > t.val; }
}; priority_queue<Node> q;
inline void Dijkstra(int S) {
	_for (i, 1, ncnt)  vis[i] = 0, dis[i] = INF; q.push((Node){S, dis[S] = 0});
	while (q.size()) {
		int u = q.top().id; q.pop();
		if (vis[u])  continue; vis[u] = 1;
		for (PII v : G[u])  if (v.fi && dis[v.fi] > dis[u] + v.se)  q.push((Node){v.fi, dis[v.fi] = dis[u] + v.se});
	}
} void dfs(int u, int fa) {
	dep[u] = dep[fa] + 1, UP[u][0] = fa, in[u][0] = ++ ncnt, G[ncnt].pb(mp(u, 0)), G[ncnt].pb(mp(fa, 0)), out[u][0] = ++ ncnt, G[u].pb(mp(ncnt, 0)), G[fa].pb(mp(ncnt, 0));
	_for (i, 1, L)  UP[u][i] = UP[UP[u][i - 1]][i - 1], in[u][i] = ++ ncnt, G[ncnt].pb(mp(in[u][i - 1], 0)), G[ncnt].pb(mp(in[UP[u][i - 1]][i - 1], 0)), out[u][i] = ++ ncnt, G[out[u][i - 1]].pb(mp(ncnt, 0)), G[out[UP[u][i - 1]][i - 1]].pb(mp(ncnt, 0));
	for (int v : Tr[u])  if (v ^ fa)  dfs(v, u);
} inline void add_path_out(int u, int v, int o) {
	if (dep[u] < dep[v])  swap(u, v); G[v].pb(mp(o, 0));
	_all (i, L, 0)  if (dep[UP[u][i]] >= dep[v])  G[out[u][i]].pb(mp(o, 0)), u = UP[u][i];
	if (u ^ v) { _all (i, L, 0)  if (UP[u][i] ^ UP[v][i])  G[out[u][i]].pb(mp(o, 0)), G[out[v][i]].pb(mp(o, 0)), u = UP[u][i], v = UP[v][i]; G[out[u][0]].pb(mp(o, 0)); }
} inline void add_path_in(int u, int v, int o) {
	if (dep[u] < dep[v])  swap(u, v); G[o].pb(mp(v, 0));
	_all (i, L, 0)  if (dep[UP[u][i]] >= dep[v])  G[o].pb(mp(in[u][i], 0)), u = UP[u][i];
	if (u ^ v) { _all (i, L, 0)  if (UP[u][i] ^ UP[v][i])  G[o].pb(mp(in[u][i], 0)), G[o].pb(mp(in[v][i], 0)), u = UP[u][i], v = UP[v][i]; G[o].pb(mp(in[u][0], 0)); }
} int main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> Q >> S, ncnt = n, L = (int)floor(log2(n)), DSU.init(); int op, u1, v1, u2, v2, w;
	while (Q -- ) {
		cin >> op >> u1 >> v1;
		if (op == 1) {
			cin >> u2 >> v2 >> w;
			if (DSU.con(u1, v1) && DSU.con(u2, v2))  tmp[ ++ qcnt] = (OP2){u1, v1, u2, v2, w};
		} else {
			cin >> w;
			if ( ! DSU.con(u1, v1))  Tr[u1].pb(v1), Tr[v1].pb(u1), G[u1].pb(mp(v1, w)), G[v1].pb(mp(u1, w)), DSU.merge(u1, v1);
		}
	} _for (i, 1, n)  if ( ! dep[i])  dfs(i, 0);
	_for (i, 1, qcnt)  add_path_out(tmp[i].u1, tmp[i].v1, ++ ncnt), add_path_in(tmp[i].u2, tmp[i].v2, ++ ncnt), G[ncnt - 1].pb(mp(ncnt, tmp[i].w)); Dijkstra(S);
	_for (i, 1, n)  cout << (dis[i] == INF ? - 1 : dis[i]) << " "; cout << "\n";
	return 0;
}

P3783 [SDOI2017] 天才黑客

考虑边转成点。每条边拆成入点和出点。连边的时候枚举一下原图上的点,然后连接相邻边的入点和出点即可。

这样连边边数最高可能达到 \(O(m^2)\),无法通过。

考虑两条边的边权实际上是在 trie 树上的 LCA 的深度。而对于每个点来说,假设其相邻的边有 \(x\) 条,那么 LCA 的可能位置最多有 \(x\) 个(根据虚树的一些理论)。按照 dfn 序排序后,还有一个重要性质:

\[\text{dep}(\text{LCA}_{i = l}^r a_i) = \min_{i = l}^{r - 1} \text{dep}(\text{LCA}(a_i, a_{i + 1})) \]

所以说如果一段区间包含了最小值的边,那么其他边是无意义的(最短路)。

考虑前缀后缀优化连边。具体的,对于每一条边开四个点 \(\text{pre_in}, \text{pre_out}, \text{suf_in}, \text{suf_out}\),分别表示前缀入点、前缀出点、后缀入点、后缀出点。然后就是前缀后缀相邻的连一些边,相邻的出点向入点连一些边,入点出点分别向原来的边连边。

这样的边数是 \(O(m)\) 级别的。时间复杂度 \(O(m \log m)\),瓶颈在于 sort 和 Dijkstra。

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define _all(i, a, b)  for (int i = (a); i >= (b); i -- )
#define ll long long
#define PII pair<int, int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
const int N = 5e4 + 5, M = 2e4 + 5, K = 10 * N; const ll INF = (ll)1e18 + 5;
int n, m, k, S, ncnt, dfn_cnt, vis[K], pre_in[N], pre_out[N], suf_in[N], suf_out[N], sz[M], son[M], dep[M], top[M], dfn[M], rk[M], fa[M]; vector<int> in[N], out[N], Tr[M]; vector<PII > ee, G[K]; ll dis[K], ans[K];
struct Node {
	int id; ll val;
	inline bool operator < (const Node & t) const { return val > t.val; }
}; priority_queue<Node> q;
struct Edge { int a, b, c, d; } e[N];
void dfs1(int u) {
	sz[u] = 1, son[u] = - 1;
	for (int v : Tr[u])  if (v ^ fa[u]) {
		fa[v] = u, dep[v] = dep[u] + 1, dfs1(v), sz[u] += sz[v];
		if ( ! ~ son[u] || sz[son[u]] < sz[v])  son[u] = v;
	}
} void dfs2(int u, int Top) {
	rk[dfn[u] = ++ dfn_cnt] = u, top[u] = Top;
	if ( ~ son[u])  dfs2(son[u], Top);
	for (int v : Tr[u])  if (v ^ son[u] && v ^ fa[u])  dfs2(v, v);
} inline int lca(int u, int v) {
	int U = top[u], V = top[v];
	while (U ^ V) {
		if (dep[U] >= dep[V])  u = fa[U];
		else  v = fa[V]; U = top[u], V = top[v];
	} return (dep[u] < dep[v] ? u : v);
} inline void Dijkstra() {
	_for (i, 0, ncnt)  dis[i] = INF, vis[i] = 0; q.push((Node){S, dis[S] = 0});
	while (q.size()) {
		int u = q.top().id; q.pop();
		if (vis[u])  continue; vis[u] = 1;
		for (PII v : G[u])  if (dis[v.fi] > dis[u] + v.se)  q.push((Node){v.fi, dis[v.fi] = dis[u] + v.se});
	}
} inline void clear() {
	S = dfn_cnt = 0, ncnt = m << 1;
	_for (i, 0, K - 1)  G[i].clear();
	_for (i, 1, n)  in[i].clear(), out[i].clear(), ans[i] = INF;
	_for (i, 1, k)  Tr[i].clear();
} inline void solve() {
	cin >> n >> m >> k, clear(); int u, v, w;
	_for (i, 1, m)  cin >> e[i].a >> e[i].b >> e[i].c >> e[i].d, out[e[i].a].pb(i), in[e[i].b].pb(i), G[i].pb(mp(i + m, e[i].c));
	_for (i, 2, k)  cin >> u >> v >> w, Tr[u].pb(v); dfs1(1), dfs2(1, 1);  // trie
	_for (i, 1, m)  if (e[i].a == 1)  G[S].pb(mp(i, 0));
	_for (i, 1, n) {
		ee.clear();
		for (int j : in[i])  ee.pb(mp(e[j].d, j + m));
		for (int j : out[i])  ee.pb(mp(e[j].d, j)); sort(ee.begin(), ee.end(), [&] (PII x, PII y) { return dfn[x.fi] < dfn[y.fi]; });  // build virtual tree
		_for (j, 0, (int)ee.size() - 1)  pre_in[j] = ++ ncnt, pre_out[j] = ++ ncnt, suf_in[j] = ++ ncnt, suf_out[j] = ++ ncnt;
		_for (j, 0, (int)ee.size() - 2)  G[pre_in[j + 1]].pb(mp(pre_in[j], 0)), G[pre_out[j]].pb(mp(pre_out[j + 1], 0)), G[suf_in[j]].pb(mp(suf_in[j + 1], 0)), G[suf_out[j + 1]].pb(mp(suf_out[j], 0)), G[suf_out[j + 1]].pb(mp(pre_in[j], w = dep[lca(ee[j].fi, ee[j + 1].fi)])), G[pre_out[j]].pb(mp(suf_in[j + 1], w));
		_for (j, 0, (int)ee.size() - 1) {
			if (ee[j].se <= m)  G[suf_in[j]].pb(mp(ee[j].se, 0)), G[pre_in[j]].pb(mp(ee[j].se, 0));
			else  G[ee[j].se].pb(mp(suf_out[j], 0)), G[ee[j].se].pb(mp(pre_out[j], 0));
		}
	} Dijkstra();
	_for (i, 1, m)  ans[e[i].b] = min(ans[e[i].b], dis[i + m]);
	_for (i, 2, n)  cout << ans[i] << "\n";
} signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	int T; cin >> T; while (T -- )  solve();
	return 0;
}

P3588 [POI 2015] PUS

较为简单的题目。考虑将大于关系转化为连边。线段树优化连边,总共 \(\sum k \log n\) 条边。

如果连成环了且环上边权和非零,一定无解。转化为 DAG 上 DP。复杂度 \(O((n + \sum k) \log n)\)

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define _all(i, a, b)  for (int i = (a); i >= (b); i -- )
#define ll long long
#define ld long double
#define PII pair<int, int>
#define fi first
#define se second
#define mp make_pair
#define pb push_back
using namespace std;
const int N = 3e5 + 5, K = N * 10;
int n, ncnt, s, m, col[K], flag[K], in_deg[K], vis[K], val[K]; vector<PII > G[K]; vector<int> node; queue<int> q;
inline void chkmin(int & x, int y) { x = x < y ? x : y; }
inline void NIE() { cout << "NIE\n", exit(0); }
struct Segment_Tree {
	#define lc (p << 1)
	#define rc (p << 1 | 1)
	#define mid ((tr[p].l + tr[p].r) >> 1)
	struct Tree { int l, r; } tr[N << 2];
	inline int len(int p) { return tr[p].r - tr[p].l + 1; }
	inline bool In(int p, int l, int r) { return l <= tr[p].l && tr[p].r <= r; }
	inline int id(int p) { return len(p) == 1 ? tr[p].l : p + n; }
	void build(int p, int l, int r) {
		tr[p].l = l, tr[p].r = r;
		if (p > 1)  G[id(p >> 1)].pb(mp(id(p), 0)), in_deg[id(p)] ++ ;
		if (l ^ r)  build(lc, l, mid), build(rc, mid + 1, r);
	} void query(int p, int l, int r, vector<int> & vec) {
		if (l > r)  return ;
		if (In(p, l, r))  return vec.push_back(id(p));
		if (l <= mid)  query(lc, l, r, vec);
		if (r > mid)  query(rc, l, r, vec);
	}
	#undef lc
	#undef rc
	#undef mid
} SGT;
void dfs(int u) {
	vis[u] = col[u] = 1;
	for (PII v : G[u]) {
		if (vis[v.fi])  NIE();
		if ( ! col[v.fi])  dfs(v.fi);
	} vis[u] = 0;
} signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> s >> m, ncnt = 5 * n, SGT.build(1, 1, n); int l, r, k, x, lst;
	while (s -- ) {
		cin >> x >> k;
		if (k < 1 || k > 1e9 || (val[x] && val[x] ^ k))  NIE(); val[x] = k, flag[x] = 1;
	} while (m -- ) {
		cin >> l >> r >> k, lst = l - 1, ncnt += 2, G[ncnt - 1].pb(mp(ncnt, 1)), in_deg[ncnt] ++ , node.clear();
		while (k -- )  cin >> x, SGT.query(1, lst + 1, x - 1, node), G[x].pb(mp(ncnt - 1, 0)), in_deg[ncnt - 1] ++ , lst = x; SGT.query(1, lst + 1, r, node);
		for (int i : node)  G[ncnt].pb(mp(i, 0)), in_deg[i] ++ ;
	} _for (i, 1, ncnt) { if ( ! col[i])  dfs(i); val[i] = val[i] ? val[i] : 1e9; }
	_for (i, 1, ncnt)  if ( ! in_deg[i])  q.push(i);
	while (q.size()) {
		int u = q.front(); q.pop();
		for (PII v : G[u]) {
			if (flag[v.fi] && val[u] - v.se < val[v.fi])  NIE(); chkmin(val[v.fi], val[u] - v.se);
			if (val[v.fi] < 1)  NIE();
			if ( ! ( -- in_deg[v.fi]))  q.push(v.fi);
		}
	} cout << "TAK\n";
	_for (i, 1, n)  cout << val[i] << " "; cout << "\n";
	return 0;
}

P7215 [JOISC 2020] 首都 / AT_joisc2020_j 首都 (Capital City)

考虑如果两个属于 \(i\) 的点的路径上含有属于 \(j\) 的点,则连边 \(i \to j\)。这样连边后缩点,一个强联通分量想要成为答案,必然要将强连通分量中所有点都归为一类(原因显然),操作次数是 点的个数减一。

注意到缩点之后是 DAG,如果有连边 \(x \to y\)\(x, y\) 都为强连通分量),那么如果想让 \(x\) 中点成为答案,得将 \(y\) 中点 merge 到 \(x\) 中才可以。所以选择 \(y\) 显然优于选 \(x\)

所以答案为 所有出度为 \(0\) 的强连通分量点的个数 \(\min\) 减去 \(1\)

树剖 + 线段树优化建图。复杂度 \(O(n \log^2 n)\)

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define _all(i, a, b)  for (int i = (a); i >= (b); i -- )
#define ll long long
#define pb push_back
using namespace std;
const int N = 1e6 + 5, INF = 2e9 + 5;
int n, m, ans = INF, dfn_cnt, ncnt, scc, Top, stk[N], bel[N], in_stack[N], tot[N], low[N], sz[N], son[N], vis[N], dep[N], top[N], dfn[N], rnk[N], fa[N], col[N], out_deg[N]; vector<int> Tr[N], C[N], G[N];
inline void chkmin(int & x, int y) { x = x < y ? x : y; }
inline void chkmax(int & x, int y) { x = x > y ? x : y; }
struct Segment_Tree {
	#define lc (p << 1)
	#define rc (p << 1 | 1)
	#define mid ((tr[p].l + tr[p].r) >> 1)
	struct Tree { int l, r; } tr[N << 2];
	inline int len(int p) { return tr[p].r - tr[p].l + 1; }
	inline bool In(int p, int l, int r) { return l <= tr[p].l && tr[p].r <= r; }
	inline int id(int p) { return len(p) == 1 ? col[rnk[tr[p].l]] : p + m; }
	void build(int p, int l, int r) {
		tr[p].l = l, tr[p].r = r, vis[id(p)] = 1;
		if (p > 1)  G[id(p >> 1)].pb(id(p));
		if (l == r)  return ; build(lc, l, mid), build(rc, mid + 1, r);
	} void query(int p, int l, int r, int k) {
		if (l > r)  return ;
		if (In(p, l, r))  return G[k].pb(id(p)), void();
		if (l <= mid)  query(lc, l, r, k);
		if (r > mid)  query(rc, l, r, k);
	}
	#undef lc
	#undef rc
	#undef mid
} SGT;
void dfs1(int u) {
	sz[u] = 1, son[u] = - 1;
	for (int v : Tr[u])  if (v ^ fa[u]) {
		fa[v] = u, dep[v] = dep[u] + 1, dfs1(v), sz[u] += sz[v];
		if ( ! ~ son[u] || sz[son[u]] < sz[v])  son[u] = v;
	}
} void dfs2(int u, int Top) {
	rnk[dfn[u] = ++ dfn_cnt] = u, top[u] = Top;
	if ( ~ son[u])  dfs2(son[u], Top);
	for (int v : Tr[u])  if (v ^ son[u] && v ^ fa[u])  dfs2(v, v);
} inline void add_edge(int u, int v, int k) {
	int U = top[u], V = top[v];
	while (U ^ V) {
		if (dep[U] >= dep[V])  SGT.query(1, dfn[U], dfn[u], k), u = fa[U];
		else  SGT.query(1, dfn[V], dfn[v], k), v = fa[V]; U = top[u], V = top[v];
	} SGT.query(1, min(dfn[u], dfn[v]), max(dfn[u], dfn[v]), k);
} inline void Tarjan(int u) {
	dfn[u] = low[u] = ++ dfn_cnt, in_stack[u] = 1, stk[ ++ Top] = u;
	for (int v : G[u]) {
		if ( ! dfn[v])  Tarjan(v), low[u] = min(low[u], low[v]);
		else if (in_stack[v])  low[u] = min(low[u], dfn[v]);
	} if (low[u] == dfn[u] && vis[u]) {
		scc ++ ;
		do { bel[stk[Top]] = scc, in_stack[stk[Top]] = 0, tot[scc] += stk[Top] <= m; } while (stk[Top -- ] ^ u);
	}
}
signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n >> m, ncnt = m + 4 * n; int u, v;
	_for (i, 1, m)  vis[i] = 1;
	_for (i, 2, n)  cin >> u >> v, Tr[u].pb(v), Tr[v].pb(u); dfs1(1), dfs2(1, 1);
	_for (i, 1, n)  cin >> col[i], C[col[i]].pb(i); SGT.build(1, 1, n);
	_for (i, 1, m) {
		sort(C[i].begin(), C[i].end(), [&] (int x, int y) { return dfn[x] < dfn[y]; });
		_for (j, 0, (int)C[i].size() - 2)  add_edge(C[i][j], C[i][j + 1], i);
	} dfn_cnt = 0;
	_for (i, 1, n)  dfn[i] = 0;
	_for (i, 1, ncnt)  if (vis[i] && ! dfn[i])  Tarjan(i);
	_for (u, 1, ncnt)  for (int v : G[u])  if (bel[u] ^ bel[v])  out_deg[bel[u]] ++ ;
	_for (i, 1, scc)  if ( ! out_deg[i])  chkmin(ans, tot[i]); cout << ans - 1 << "\n";
	return 0;
}

P5025 [SNOI2017] 炸弹

差一步啊 /ll。

考虑每个炸弹第一轮能引爆的点是一段区间,线段树优化连边。转化为每个点能到达的点数。

首先缩个点,转化为 DAG 问题。注意到如果是一般 DAG 这个问题是 NPC 的。

但考虑到最后每个点的答案一定是区间,我们拓扑的时候只需记录能到达的 \(l, r\) 即可。

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define ll long long
#define pb push_back
#define PII pair<int, int>
#define mp make_pair
using namespace std;
const int N = 3e6 + 5, P = 1e9 + 7; const ll INF = (ll)1e18 + 5;
int n, Ans, dfn_cnt, ncnt, scc, top, ansl[N], ansr[N], vis[N], ans[N], stk[N], bel[N], in_stack[N], low[N], dfn[N], f[N], in_deg[N]; ll x[N], r[N]; vector<int> vec[N], G[N], G2[N]; map<PII, int> occur; queue<int> q;
inline int mul(int x, int y) { return 1ll * x * y % P; }
inline void Add(int & x, int y) { x = x + y >= P ? x + y - P : x + y; }
inline void chkmin(int & x, int y) { x = x < y ? x : y; }
inline void chkmax(int & x, int y) { x = x > y ? x : y; }
struct Segment_Tree {
	#define lc (p << 1)
	#define rc (p << 1 | 1)
	#define mid ((tr[p].l + tr[p].r) >> 1)
	struct Tree { int l, r; } tr[N << 2];
	inline int len(int p) { return tr[p].r - tr[p].l + 1; }
	inline bool In(int p, int l, int r) { return l <= tr[p].l && tr[p].r <= r; }
	inline int id(int p) { return len(p) == 1 ? tr[p].l : p + n; }
	void build(int p, int l, int r) {
		tr[p].l = l, tr[p].r = r, vis[id(p)] = 1;
		if (p > 1)  G[id(p >> 1)].pb(id(p));
		if (l ^ r)  build(lc, l, mid), build(rc, mid + 1, r);
	} void query(int p, int l, int r, int k) {
		if (l > r)  return ;
		if (In(p, l, r))  return G[k].pb(id(p)), void();
		if (l <= mid)  query(lc, l, r, k);
		if (r > mid)  query(rc, l, r, k);
	}
	#undef lc
	#undef rc
	#undef mid
} SGT;
inline void Tarjan(int u) {
	dfn[u] = low[u] = ++ dfn_cnt, in_stack[u] = 1, stk[ ++ top] = u;
	for (int v : G[u]) {
		if ( ! dfn[v])  Tarjan(v), low[u] = min(low[u], low[v]);
		else if (in_stack[v])  low[u] = min(low[u], dfn[v]);
	} if (low[u] == dfn[u]) {
		scc ++ ;
		do { bel[stk[top]] = scc, in_stack[stk[top]] = 0, f[scc] += stk[top] <= n, vec[scc].pb(stk[top]), chkmin(ansl[scc], stk[top] <= n ? stk[top] : N), chkmax(ansr[scc], stk[top] <= n ? stk[top] : - N); } while (stk[top -- ] ^ u);
	}
}
signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n, SGT.build(1, 1, n); int L, R, mid, pos;
	_for (i, 1, n)  cin >> x[i] >> r[i], vis[i] = 1;
	_for (i, 1, n) {
		L = 1, R = i - 1, pos = i;
		while (L <= R) {
			mid = (L + R) >> 1;
			if (x[mid] >= x[i] - r[i])  pos = mid, R = mid - 1;
			else  L = mid + 1;
		} SGT.query(1, pos, i - 1, i), pos = i, L = i + 1, R = n;
		while (L <= R) {
			mid = (L + R) >> 1;
			if (x[mid] <= x[i] + r[i])  pos = mid, L = mid + 1;
			else  R = mid - 1;
		} SGT.query(1, i + 1, pos, i);
	} _for (i, 1, 5 * n)  ansl[i] = N, ansr[i] = - N;
	_for (i, 1, 5 * n)  if (vis[i] && ! dfn[i])  Tarjan(i);
	_for (u, 1, 5 * n)  for (int v : G[u])  if (vis[u] && vis[v] && bel[u] ^ bel[v] && ! occur[mp(bel[u], bel[v])])  occur[mp(bel[u], bel[v])] = 1, G2[bel[v]].pb(bel[u]), in_deg[bel[u]] ++ ;
	_for (i, 1, scc)  if ( ! in_deg[i])  q.push(i);
	while (q.size()) {
		int u = q.front(); q.pop();
		for (int v : G2[u]) {
			chkmin(ansl[v], ansl[u]), chkmax(ansr[v], ansr[u]);
			if ( ! ( -- in_deg[v]))  q.push(v);
		}
	} _for (i, 1, scc)  for (int j : vec[i])  ans[j] = ansr[i] - ansl[i] + 1;
	_for (i, 1, n)  Add(Ans, mul(i, ans[i])); cout << Ans << "\n";
	return 0;
}

CF1007D Ants

口胡了一下,不想写了。

考虑 2-sat,树剖 + 线段树优化建图即可。复杂度应该是 \(O(m \log^2 n)\)

AT_arc069_d [ARC069F] Flags

线段树优化 2-sat 建图。

妈的 WA 了两个点调不过去了,不想调了。

Submission

Code
#include <bits/stdc++.h>
#define _for(i, a, b)  for (int i = (a); i <= (b); i ++ )
#define _all(i, a, b)  for (int i = (a); i >= (b); i -- )
#define ll long long
#define PII pair<int, int>
#define mp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const int N = 1e5 + 5;
int n, dfn_cnt, scc, top, x[N], y[N], stk[N], bel[N], in_stack[N], low[N], dfn[N]; PII a[N], b[N]; vector<int> G[N];
struct Segment_Tree {
	#define lc (p << 1)
	#define rc (p << 1 | 1)
	#define mid ((tr[p].l + tr[p].r) >> 1)
	int op, h; struct Tree { int l, r, val; } tr[N << 2];
	inline int len(int p) { return tr[p].r - tr[p].l + 1; }
	inline bool In(int p, int l, int r) { return l <= tr[p].l && tr[p].r <= r; }
	inline int id(int p) { return len(p) == 1 ? tr[p].val : p + h; }
	void build(int p, int l, int r) {
		tr[p].l = l, tr[p].r = r, tr[p].val = op ? b[l].se : a[l].se + n;
		if (p > 1)  G[id(p >> 1)].pb(id(p));
		if (l ^ r)  build(lc, l, mid), build(rc, mid + 1, r);
	} void query(int p, int l, int r, int k) {
		if (l > r)  return ;
		if (In(p, l, r))  return G[k].pb(id(p)), void();
		if (l <= mid)  query(lc, l, r, k);
		if (r > mid)  query(rc, l, r, k);
	}
	#undef lc
	#undef rc
	#undef mid
} SGT[2];
inline void Tarjan(int u) {
	dfn[u] = low[u] = ++ dfn_cnt, in_stack[u] = 1, stk[ ++ top] = u;
	for (int v : G[u]) {
		if ( ! dfn[v])  Tarjan(v), low[u] = min(low[u], low[v]);
		else if (in_stack[v])  low[u] = min(low[u], dfn[v]);
	} if (low[u] == dfn[u]) {
		scc ++ ;
		do { bel[stk[top]] = scc, in_stack[stk[top]] = 0; } while (stk[top -- ] ^ u);
	}
}
#define mid ((l + r) >> 1)
inline bool valid(int v) {
	int l, r, pos, posl, posr;
	_for (i, 1, 10 * n)  bel[i] = in_stack[i] = low[i] = dfn[i] = stk[i] = 0, G[i].clear(); dfn_cnt = scc = top = 0, SGT[0].build(1, 1, n), SGT[1].build(1, 1, n);
	_for (i, 1, n) {
		l = 1, r = n, pos = 0;
		while (l <= r)  a[mid].fi >= x[i] ? pos = mid, r = mid - 1 : l = mid + 1; l = 1, r = pos - 1, posl = pos;  // x -> x
		while (l <= r)  a[mid].fi > x[i] - v ? posl = mid, r = mid - 1 : l = mid + 1; SGT[0].query(1, posl, pos - 1, i), l = pos + 1, r = n, posr = pos;
		while (l <= r)  a[mid].fi < x[i] + v ? posr = mid, l = mid + 1 : r = mid - 1; SGT[0].query(1, pos + 1, posr, i), l = 1, r = n, pos = 0;
		while (l <= r)  b[mid].fi >= y[i] ? pos = mid, r = mid - 1 : l = mid + 1; l = 1, r = n, posl = n + 1;  // x -> y
		while (l <= r)  b[mid].fi > x[i] - v ? posl = mid, r = mid - 1 : l = mid + 1; l = posl, r = n, posr = 0;
		while (l <= r)  b[mid].fi < x[i] + v ? posr = mid, l = mid + 1 : r = mid - 1;
		if (posl <= posr) {
			if (pos < posl || pos > posr)  SGT[1].query(1, posl, posr, i);
			else  SGT[1].query(1, posl, pos - 1, i), SGT[1].query(1, pos + 1, posr, i);
		} l = 1, r = n, pos = 0;
		while (l <= r)  a[mid].fi >= x[i] ? pos = mid, r = mid - 1 : l = mid + 1; l = 1, r = n, posl = n + 1;  // y -> x
		while (l <= r)  a[mid].fi > y[i] - v ? posl = mid, r = mid - 1 : l = mid + 1; l = posl, r = n, posr = 0;
		while (l <= r)  a[mid].fi < y[i] + v ? posr = mid, l = mid + 1 : r = mid - 1;
		if (posl <= posr) {
			if (pos < posl || pos > posr)  SGT[0].query(1, posl, posr, i + n);
			else  SGT[0].query(1, posl, pos - 1, i + n), SGT[0].query(1, pos + 1, posr, i + n);
		} l = 1, r = n, pos = 0;
		while (l <= r)  b[mid].fi >= y[i] ? pos = mid, r = mid - 1 : l = mid + 1; l = 1, r = pos - 1, posl = pos;  // y -> y
		while (l <= r)  b[mid].fi > y[i] - v ? posl = mid, r = mid - 1 : l = mid + 1; SGT[1].query(1, posl, pos - 1, i + n), l = pos + 1, r = n, posr = pos;
		while (l <= r)  b[mid].fi < y[i] + v ? posr = mid, l = mid + 1 : r = mid - 1; SGT[1].query(1, pos + 1, posr, i + n);
	} _for (i, 1, n << 1)  if ( ! dfn[i])  Tarjan(i);
	_for (i, 1, n)  if (bel[i] == bel[i + n])  return 0; return 1;
} signed main() {
	ios :: sync_with_stdio(false), cin.tie(0), cout.tie(0);
	cin >> n; int l = 0, r = 1e9, ans = 0; SGT[0].op = 0, SGT[1].op = 1, SGT[0].h = 2 * n, SGT[1].h = 6 * n;
	_for (i, 1, n)  cin >> x[i] >> y[i], a[i] = mp(x[i], i), b[i] = mp(y[i], i); sort(a + 1, a + n + 1), sort(b + 1, b + n + 1);
	while (l <= r)  valid(mid) ? ans = mid, l = mid + 1 : r = mid - 1; cout << ans << "\n";
	return 0;
}

P7452 [THUSC 2017] 换桌

线段树优化网络流建图。

posted @ 2025-03-13 16:11  Ray_Wu  阅读(32)  评论(0)    收藏  举报