P5025 [SNOI2017] 炸弹 题解

做法

建图,求每个点能到达的节点个数。

直接连边是 \(O(n^2)\) 的。发现与每个点直接相连的点在一个区间内,因此考虑线段树优化建图。

由于要求每个点能到达的节点个数,因此考虑缩点,进行 DAG 上的 DP。

一开始的想法,转化为求能到达每个点的节点编号和,并使用拓扑排序。但发现这样会算重。

举个例子,如果 \(x\rightarrow y\)\(x\rightarrow z\)\(y\rightarrow z\),那么在计算 \(z\) 的答案时 \(x\) 会算两次。

现在看来无法通过一般的转移来求答案。但因为与每个点直接相连的点在一个区间内,所以每个点能走到的点也在一个区间内。因此可对每个点维护一个区间,转移即对区间取并,实现上即取左端点最小值和右端点最大值。

即:令 \(f_i\) 表示节点 \(i\) 能走到的节点区间,有:

\[\large f_i=\bigcup_{u\rightarrow v}f_v \]

实现

#include <iostream>
#include <queue>
#include <algorithm>
#define N 3000005
#define M 20000005
#define mod 1000000007
#define int long long
int n,a[N],b[N],col[N],cn,len,rt,id[N],in[N],sz[N],sum[N],vis[N],ans;
std::queue<int> q;
struct Graph
{
	int hed[N],tal[M],nxt[M],cnte;
	void de(int u,int v) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte;}
} G,R,T;
void de(int u,int v) {G.de(u,v),R.de(v,u);}
struct Seq {int l,r;} f[N];
namespace SCC
{
	int vs[N],li[N],idx;
	void dfs1(int x)
	{
		vs[x]=1;
		for(int i=G.hed[x];i;i=G.nxt[i]) if(!vs[G.tal[i]]) dfs1(G.tal[i]);
		li[++idx]=x;
	}
	void dfs2(int x)
	{
		col[x]=cn;
		for(int i=R.hed[x];i;i=R.nxt[i]) if(!col[R.tal[i]]) dfs2(R.tal[i]);
	}
	void Kosaraju()
	{
		for(int i=1;i<=len;i++) if(!vs[i]) dfs1(i);
		for(int i=len;i>=1;i--) if(!col[li[i]]) cn++,dfs2(li[i]);
	}
};
int lbd(int x)
{
	int l=1,r=n,ret=-1;
	while(l<=r) {int mid=l+r>>1;a[mid]>=x?(r=mid-1,ret=mid):(l=mid+1);}
	return ret;
}
int rbd(int x)
{
	int l=1,r=n,ret=-1;
	while(l<=r) {int mid=l+r>>1;a[mid]<=x?(l=mid+1,ret=mid):(r=mid-1);}
	return ret;
}
namespace SGT
{
	#define mid (lb+rb>>1)
	int ls[N],rs[N];
	int build(int lb,int rb)
	{
		int x=++len;
		if(lb==rb) return id[lb]=x;
		return ls[x]=build(lb,mid),rs[x]=build(mid+1,rb),de(x,ls[x]),de(x,rs[x]),x;
	}
	void adde(int x,int l,int r,int t,int lb,int rb)
	{
		if(t==x) return;
		if(l<=lb&&rb<=r) return /*printf("de %d %d\n",t,x),*/de(t,x),void();
		if(l<=mid) adde(ls[x],l,r,t,lb,mid);
		if(r>mid) adde(rs[x],l,r,t,mid+1,rb);
	}
	#undef mid
};
void solve(int x)
{
	if(vis[x]) return;
	vis[x]=1;
	for(int i=T.hed[x];i;i=T.nxt[i])
		solve(T.tal[i]),
		f[x].l=std::min(f[x].l,f[T.tal[i]].l),f[x].r=std::max(f[x].r,f[T.tal[i]].r);
	(ans+=(f[x].r-f[x].l+1)*sum[x]%mod)%=mod;
}
signed main()
{
	std::ios::sync_with_stdio(0);
	std::cin.tie(0),std::cout.tie(0);
	std::cin>>n;
	for(int i=1;i<=n;i++) std::cin>>a[i]>>b[i];
	rt=SGT::build(1,n);
	for(int i=1,l,r;i<=n;i++) l=lbd(a[i]-b[i]),r=rbd(a[i]+b[i]),SGT::adde(rt,l,r,id[i],1,n);//,printf("%d: %d %d\n",i,l,r);
	SCC::Kosaraju();
	for(int i=1;i<=cn;i++) f[i].l=1e9;
	for(int i=1;i<=len;i++) for(int j=G.hed[i];j;j=G.nxt[j])
		if(col[i]!=col[G.tal[j]]) T.de(col[i],col[G.tal[j]]),in[col[G.tal[j]]]++;
	for(int i=1;i<=n;i++) sz[col[id[i]]]++,(sum[col[id[i]]]+=i)%=mod,
		f[col[id[i]]].l=std::min(f[col[id[i]]].l,i),f[col[id[i]]].r=std::max(f[col[id[i]]].r,i);
	//for(int i=1;i<=cn;i++) (ans+=sz[i]*sum[i]%mod)%=mod;
	for(int i=1;i<=cn;i++) if(!vis[i]) solve(i);
	std::cout<<ans;
}

后记

Kosaraju 板子写挂了能得 \(68\)
Tarjan 比 Kosaraju 常数小

posted @ 2025-07-03 16:15  整齐的艾萨克  阅读(15)  评论(0)    收藏  举报