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 常数小

浙公网安备 33010602011771号