cunzai_zsy0531

关注我

P5025 [SNOI2017]炸弹 题解

题面

这题很有意思,正解做法是线段树优化建图之后跑tarjan强连通分量然后在DAG上做一些奇奇怪怪的统计,但是这题有神仙 \(O(n)\) 做法!(虽然我不会证明但是跑的的确很快啊)

说的是,首先注意到先点一个,最后爆炸的炸弹一定是一个区间。所以现在问题转化成了对于每个 \(i\) 求爆炸的左右端点 \(l_i,r_i\)

考虑四种情况:向左炸了之后能继续向左;向左之后可以向右;向右之后继续向右;向右之后再向左。

对于第一种情况:从左到右对于每个 \(i\),做这些事情:

  1. 判断 \(i\) 能不能炸到 \(l_i-1\),或者 \(l_i\) 是否已经是 \(1\)
  2. 如果第一步还能继续,那么将 \(i\) 的爆炸半径 \(R_i\)\(l_i-1\) 的爆炸半径(向右的)取个 \(\max\),相当于是处理向左再向右的情况。
  3. \(l_i:=l_{l_i-1}\),相当于是处理向左再向左的情况。

对于第三种情况:从右向左扫每个 \(i\),其余都差不多,在第 \(2\) 步不需要再改 \(R_i\) 了,但是需要通过 \(l_i=\max(l_i,l_{r_i+1})\) 来处理第四种情况。

这样所有情况讨论完了,题解里说这个是“隐形单调栈”,反正一顿分析下来这个复杂度的确是 \(O(n)\) 的。神仙啊!

其实这个题感觉有一些特殊性质,直接线段树优化建图做的确感觉不是最优解。有些题目的确需要多思考!、

点击查看代码
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long ll;
inline ll min(const ll &a,const ll &b){return a<b?a:b;}
const int N=5e5+13,mod=1e9+7;
int n,l[N],r[N];
ll a[N],R[N]; 
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;++i) scanf("%lld%lld",&a[i],&R[i]);
	for(int i=1;i<=n;++i) l[i]=r[i]=i;
	for(int i=2;i<=n;++i){
		while(l[i]>1&&a[i]-a[l[i]-1]<=R[i]){
			R[i]=max(R[i],R[l[i]-1]-(a[i]-a[l[i]-1]));
			l[i]=l[l[i]-1];
		}
	}
	for(int i=n-1;i;--i){
		while(r[i]<n&&a[r[i]+1]-a[i]<=R[i]){
			l[i]=min(l[i],l[r[i]+1]);
			r[i]=r[r[i]+1];
		}
	}
	int ans=0;
	for(int i=1;i<=n;++i) ans=(ans+(ll)i*(r[i]-l[i]+1))%mod;
	printf("%d\n",ans);
	return 0;
}
posted @ 2022-05-19 16:58  cunzai_zsy0531  阅读(19)  评论(0编辑  收藏  举报