title
BZOJ 5017
LUOGU 5025
Description
在一条直线上有 \(N\) 个炸弹,每个炸弹的坐标是 \(X_i\),爆炸半径是 \(R_i\),当一个炸弹爆炸时,如果另一个炸弹所在位置 \(X_j\)满足: \(X_i-R_i≤Xj≤X_i+R_i\),那么,该炸弹也会被引爆。 现在,请你帮忙计算一下,先把第 \(i\) 个炸弹引爆,将引爆多少个炸弹呢?
答案对\(1000000007\)取模
Input
第一行,一个数字 \(N\) ,表示炸弹个数。 第 \(2 ∼ N+1\) 行,每行 \(2\) 个数字,表示 \(X_i\) ,\(R_i\),保证 \(X_i\) 严格递增。
Output
一个数字,表示 \(\sum \limits_{i=1}^n i\times i×\) 炸弹 \(i\) 能引爆的炸弹个数。
Sample Input
4
1 1
5 1
6 5
15 15
Sample Output
32
analysis
开始补 \(blog\) 了,有点难受吧,毕竟回忆起当时的想法有些困难。
- 题目较短,也有一个比较好的性质,一个炸弹能炸到的范围是一个区间,但是说实在的,我在最初的解题过程中并没有很注意这个性质,发现后也直接略过了。
- 我们考虑暴力算法,对于每个炸弹,向自己能炸到的炸弹连边,对于互相能炸到的炸弹,其能炸到的区间一定是相同的,所以用 \(tarjan\) 进行有向图缩点,对于剩下的这个 \(DAG\) 进行 \(dp\) ,统计一个点子孙的数量, \(f[i][j]\) 表示第 \(j\) 个点是否是 \(i\) 的子孙,时间和空间复杂度均为 \(O(n^2)\),不是很优。
- 考虑一个点的的子孙集合一定是一段连续的区间,所以在 \(dp\) 的时候记子节点的最大、最小编号即可,空间复杂度转成 \(O(n)\)。因为这种算法的关键在于连边太多,所以,我们就要用上面发现的所谓美好性质来搞搞事情了,考虑线段树优化建图,点数仍然为 \(O(n)\),边数优化成 \(O(nlogn)\)。
- 这是学长给我们所传授的线段树优化建图的解题方法,对于这一道题来说,并不能说是最优的解法,但是,线段树优化建图的方法确实必须要会的,\(NOI2019~D2~T1\) 的 \(72pts\) 暴力便是用这种算法的,可惜当时我没学会(现在也没学,QwQ)。
- 偷懒么,所以去学了这个 \(O(n)\) 算法。
- 连边的时候不考虑线段树优化建图,而是考虑对于每一个点找到其左侧和右侧分别离自己最近的能炸到自己的点,将这两个点向自己连边。这样很容易发现,每个点只连了两条边,复杂度 \(O(n)\)。
- 总复杂度,因为要算上离散化的复杂度,所以是 \(O(nlogn)\) 的,不过这道题好像离散化有些鸡肋,但是我也不想写个基数排序了。
- 参考资料:zhouyuheng2003。
- 综上呢,有用的技巧类,如线段树优化建图,这是必须得会的;但是对于题目本身来说,要去追究他的最优解,当然你只是拿这题来练手的,我也无所谓。
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn=5e5+10,mod=1e9+7;
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
template<typename T>inline void write(T x)
{
if (!x) { putchar('0'); return ; }
if (x<0) putchar('-'), x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) putchar(ch[num--]);
}
struct Orz{ll x,l,r;}a[maxn];
int ver[maxn<<1],Next[maxn<<1],head[maxn],len;
inline void add(int x,int y)
{
ver[++len]=y,Next[len]=head[x],head[x]=len;
}
int dfn[maxn],low[maxn],id,n;
int Stack[maxn],top;
int belong[maxn],tot;
int ls[maxn],rs[maxn];
bool instack[maxn];
inline void tarjan(int x)
{
dfn[x]=low[x]=++id;
Stack[++top]=x;
instack[x]=1;
for (int i=head[x]; i; i=Next[i])
{
int y=ver[i];
if (!dfn[y])
{
tarjan(y);
low[x]=min(low[x],low[y]);
}
else if (instack[y])
low[x]=min(low[x],dfn[y]);
}
if (low[x]==dfn[x])
{
int k;
ls[++tot]=n+1;
do
{
k=Stack[top--];
ls[tot]=min(ls[tot],(int)a[k].l);
rs[tot]=max(rs[tot],(int)a[k].r);
belong[k]=tot;
instack[k]=0;
} while (k!=x);
}
}
int vc[maxn<<1],Nc[maxn<<1],hc[maxn],deg[maxn],lc;
inline void addc(int x,int y)
{
if (x==y) return ;
vc[++lc]=y,Nc[lc]=hc[x],hc[x]=lc,++deg[y];
}
ll b[maxn];
int main()
{
read(n);
for (int i=1; i<=n; ++i) read(a[i].x),read(a[i].r),b[i]=a[i].x;
for (int i=1; i<=n; ++i)
{
a[i].l=lower_bound(b+1,b+n+1,a[i].x-a[i].r)-b;
a[i].r=upper_bound(b+1,b+n+1,a[i].x+a[i].r)-b-1;
a[i].x=lower_bound(b+1,b+n+1,a[i].x)-b;
}
for (int i=1; i<=n; ++i)
{
while (top && a[Stack[top]].r<a[i].x) --top;
if (top && a[Stack[top]].r>=a[i].x) add(Stack[top],i);
while (top && a[Stack[top]].r<=a[i].r) --top;
Stack[++top]=i;
}
top=0;
for (int i=n; i>=1; --i)
{
while (top && a[Stack[top]].l>a[i].x) --top;
if (top && a[Stack[top]].l<=a[i].x) add(Stack[top],i);
while (top && a[Stack[top]].l>=a[i].l) --top;
Stack[++top]=i;
}
for (int i=1; i<=n; ++i)
if (!dfn[i]) tarjan(i);
for (int k=1; k<=n; ++k)
for (int i=head[k]; i; i=Next[i])
{
int y=ver[i];
addc(belong[y],belong[k]);
}
queue<int>q;
for (int i=1; i<=n; ++i)
if (!deg[i]) q.push(i);
while (!q.empty())
{
int x=q.front();
q.pop();
for (int i=hc[x]; i; i=Nc[i])
{
int y=vc[i];
ls[y]=min(ls[y],ls[x]);
rs[y]=max(rs[y],rs[x]);
if (!--deg[y]) q.push(y);
}
}
ll res=0;
for (int i=1; i<=n; ++i) res=(res+1ll*i*(rs[belong[i]]-ls[belong[i]]+1))%mod;
write(res),puts("");
return 0;
}