P5025 SNOI2017 炸弹
不难看出本题是可以转化为图论模型的:建立
这样的图论模型很好地刻画了原题中引爆的传递性,题意中第
这个问题可以被转化为对于一个 DAG,对每个点
对 DAG 进行拓扑排序同时进行 dp,设
有一个想法是
所以,直接将所有儿子的
事实上,DAG 上统计点
但是本题要求我们统计点
不难发现,本题主动引爆一个炸弹后,所有被直接 / 间接引爆的炸弹构成包含主动被引爆炸弹的一段区间。这意味着,我们只需要找到引爆炸弹后,被直接 / 间接引爆的炸弹中,编号最小的那个和编号最大的那个,就可以计算出引爆炸弹的数量。
那么我们就可以将问题更换为寻找每个点
最后还有个事,就是本题暴力建边复杂度为
总结:笔者学习到了处理【在有向图上,对每个点
- 先考虑缩点,注意缩点时信息的合并。缩点之后变成 DAG,考虑 DAG 上 dp。
- 如果信息的合并方式不是可重复贡献的(比如权值和),则很可能不可做,否则(比如权值
, , )直接 dp 即可。 - 题目给出的信息要求我们计算每个点可达的权值和,可以先考虑转化成权值最值看看。
/*
* @Author: crab-in-the-northeast
* @Date: 2023-06-21 19:39:54
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2023-06-21 20:55:17
*/
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool f = true;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
f = false;
for (; isdigit(ch); ch = getchar())
x = (x << 1) + (x << 3) + ch - '0';
return f ? x : (~(x - 1));
}
inline int ls(int p) {
return p << 1;
}
inline int rs(int p) {
return p << 1 | 1;
}
inline bool gmi(int &a, int b) {
return b < a ? a = b, true : false;
}
inline bool gmx(int &a, int b) {
return b > a ? a = b, true : false;
}
const int N = (int)5e5 + 5;
int pos[N], rad[N], cnt;
struct node {
int l, r, x;
} t[N << 2];
namespace org {
std :: vector <int> G[N << 2];
}
namespace scc {
std :: vector <int> G[N << 2];
}
void build(int p, int l, int r) {
t[p].l = l;
t[p].r = r;
if (l == r) {
t[p].x = l;
return ;
}
t[p].x = ++cnt;
int mid = (l + r) >> 1;
build(ls(p), l, mid);
build(rs(p), mid + 1, r);
org :: G[t[p].x].push_back(t[ls(p)].x);
org :: G[t[p].x].push_back(t[rs(p)].x);
}
void connect(int p, int L, int R, int u) {
int l = t[p].l, r = t[p].r, x = t[p].x;
if (l == L && R == r)
return org :: G[u].push_back(x);
int mid = (l + r) >> 1;
if (R <= mid)
connect(ls(p), L, R, u);
else if (L > mid)
connect(rs(p), L, R, u);
else {
connect(ls(p), L, mid, u);
connect(rs(p), mid + 1, R, u);
}
}
int low[N << 2], dfn[N << 2], sccno[N << 2], times = 0;
int snt;
std :: stack <int> s;
void tarjan(int u) {
dfn[u] = low[u] = ++times;
s.push(u);
for (int v : org :: G[u]) {
if (!dfn[v]) {
tarjan(v);
gmi(low[u], low[v]);
} else if (!sccno[v])
gmi(low[u], dfn[v]);
}
if (low[u] == dfn[u]) {
++snt;
for (; ;) {
int x = s.top();
s.pop();
sccno[x] = snt;
if (x == u)
break;
}
}
}
int f[N << 2], g[N << 2];
int ind[N << 2];
void topsort(int n) {
std :: queue <int> q;
for (int u = 1; u <= n; ++u)
if (ind[u] == 0)
q.push(u);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : scc :: G[u]) {
--ind[v];
gmx(f[v], f[u]);
gmi(g[v], g[u]);
if (ind[v] == 0)
q.push(v);
}
}
}
signed main() {
int n = cnt = read();
for (int i = 1; i <= n; ++i) {
pos[i] = read();
rad[i] = read();
}
build(1, 1, n);
for (int i = 1; i <= n; ++i) {
int x = pos[i], d = rad[i];
int l = std :: lower_bound(pos + 1, pos + 1 + n, x - d) - pos;
int r = std :: upper_bound(pos + 1, pos + 1 + n, x + d) - pos - 1;
connect(1, l, r, i);
}
for (int u = 1; u <= (n << 2); ++u)
if (!dfn[u])
tarjan(u);
std :: memset(g, 0x3f, sizeof(g));
for (int u = 1; u <= n; ++u)
f[sccno[u]] = u;
for (int u = n; u; --u)
g[sccno[u]] = u;
for (int u = 1; u <= (n << 2); ++u)
for (int v : org :: G[u])
if (sccno[u] != sccno[v]) {
scc :: G[sccno[v]].push_back(sccno[u]);
++ind[sccno[u]];
}
topsort(n << 2);
int ans = 0;
const int mod = (int)1e9 + 7;
for (int u = 1; u <= n; ++u)
(ans += u * (f[sccno[u]] - g[sccno[u]] + 1)) %= mod;
printf("%lld\n", ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效