很直观的想法是直接把每个点向能炸到的点都连一条边,然后缩点dag再dp一下求出每个点能炸到的最左端和最右端。
可是这样空间and时间复杂度都爆炸,直接gg。
怎么优化它呢?
我们发现每个点能引爆的点一定在某个区间内,所以我们容懿想到线段树优化建图。
建一棵线段树,从父节点连向它的两个子节点一条有向边。
对于每个节点,计算出它自己爆炸的范围,然后向线段树上代表这个区间的那些点连边。
这个节点其实用线段树上的叶节点即可,不用再建新的节点。
然后跑一遍tarjan强连通分量缩点,每个新点要记录改分量里能炸到的最远的左右端点。
之后我们还有跑一点dag dp,用每个节点能到达的点更新其左右端点。
注意这里不能直接拓扑,要在反图上拓扑,因为这里直接拓扑时孩子是没有更新的(虽然能过。。。)
查询时直接返回对应scc的左右端点的差即可。
上代码:
#include <iostream> #include <cstdio> #include <vector> #include <cstring> #include <queue> #include <algorithm> using namespace std; typedef long long ll; const ll N = 5000010; const ll mod = 1e9 + 7; struct bomb{ ll pos, r; }a[N]; ll id[N]; ll n; ll dfn[N], dep, low[N], stk[N], top; ll col[N], scc; ll mx; bool vis[N]; ll L[N], R[N], Left[N], Right[N], ans; vector<ll> vec[N], G[N]; void add(int u, int v) { G[u].push_back(v); } void build(ll p, ll l, ll r) { mx = max(mx, p); L[p] = l; R[p] = r; if (l == r) { id[l] = p; return; } ll mid = (l + r) >> 1; build(p << 1, l, mid); build(p << 1 | 1, mid + 1, r); add(p, p << 1); add(p, p << 1 | 1); } void work(ll p, ll l, ll r, ll x, ll y, ll v) { if (r < x || l > y) return; if (x <= l && r <= y) { if (v == p) return; add(v, p); return; } ll mid = (l + r) >> 1; work(p << 1, l, mid, x, y, v); work(p << 1 | 1, mid + 1, r, x, y, v); } void tarjan(ll x) { dfn[x] = low[x] = ++dep; stk[++top] = x; vis[x] = 1; for (ll i = 0; i < G[x].size(); i++) { ll y = G[x][i]; if (!dfn[y]) { tarjan(y); low[x] = min(low[x], low[y]); } else if (vis[y]) { low[x] = min(low[x], dfn[y]); } } if (dfn[x] == low[x]) { col[x] = ++scc; vis[x] = 0; Left[scc] = L[x]; Right[scc] = R[x]; while (stk[top] != x) { Left[scc] = min(Left[scc], L[stk[top]]); Right[scc] = max(Right[scc], R[stk[top]]); col[stk[top]] = scc; vis[stk[top]] = 0; top--; } top--; } } ll Find1(ll l, ll r, ll v) { ll ret = 0; while (l <= r) { ll mid = (l + r) >> 1; if (a[mid].pos <= v) { ret = mid; l = mid + 1; } else { r = mid - 1; } } return ret; } ll Find2(ll l, ll r, ll v) { ll ret = 0; while (l <= r) { ll mid = (l + r) >> 1; if (a[mid].pos >= v) { ret = mid; r = mid - 1; } else { l = mid + 1; } } return ret; } void dfs(int x) { vis[x] = 1; for (int i = 0; i < vec[x].size(); i++) { int y = vec[x][i]; if (vis[y]) { Left[x] = min(Left[x], Left[y]); Right[x] = max(Right[x], Right[y]); continue; } dfs(y); Left[x] = min(Left[x], Left[y]); Right[x] = max(Right[x], Right[y]); } } ll query(ll x) { return Right[x] - Left[x] + 1; } bool cmp(bomb p, bomb q) { return p.pos < q.pos; } int main() { scanf("%lld", &n); for (ll i = 1; i <= n; i++) { scanf("%lld%lld", &a[i].pos, &a[i].r); } sort(a + 1, a + 1 + n, cmp); build(1, 1, n); for (ll i = 1; i <= n; i++) { if (a[i].r == 0) continue; ll r = Find1(i, n, a[i].pos + a[i].r); ll l = Find2(1, i, a[i].pos - a[i].r); work(1, 1, n, l, r, id[i]); Left[id[i]] = l; Right[id[i]] = r; } tarjan(1); for (ll x = 1; x <= mx; x++) { for (ll j = 0; j < G[x].size(); j++) { ll y = G[x][j]; if (col[x] != col[y]) { vec[col[x]].push_back(col[y]); } } } for (int i = 1; i <= scc; i++) { sort(vec[i].begin(), vec[i].end()); unique(vec[i].begin(), vec[i].end()); } memset(vis, 0, sizeof(vis)); for (int i = 1; i <= scc; i++) { if (!vis[i]) dfs(i); } for (ll i = 1; i <= n; i++){ ans += (i * query(col[id[i]])) % mod; ans %= mod; } cout << ans % mod; return 0; }
原文:https://www.cnblogs.com/zcr-blog/p/12860496.html