【SNOI2017】炸弹(线段树优化建边+Tarjan缩点+拓扑排序)

【SNOI2017】炸弹(线段树优化建边+Tarjan缩点+拓扑排序)_第1张图片这道题如果强行爆搜的话时间复杂度应该是N^2的,所以我们要考虑怎么优化。

线段树优化

这就是这道题的主要方法:给一个区间连边而不是区间里的每一个点,这样的话时间复杂度就会降成log的复杂度,变成了nlogn,这个复杂度在我们可接受范围之内。

具体的来讲就是已n+1为线段树的根的编号,开始建立新的节点,如果搜到了叶子节点,它的编号就变成了自己原来的编号,每当要向区间连边时,就从这个区间上的点连接包含你要连的区间的节点。

建树的代码如下:

void build(int &t,int l,int r)
{
	if(l==r)
	{
		lc[l]=rc[l]=t=l;
		return;	
	}	
	t=++ndnum;
	lc[t]=l;
	rc[t]=r;
	int mid=(l+r)/2;
	build(lson[t],l,mid);
	build(rson[t],mid+1,r);
	add(t,lson[t]);
	add(t,rson[t]);//连边
}

然后就是连接区间了:

void connect(int id,int t,int l,int r)
{
	if(l<=lc[id]&&rc[id]<=r) { add(t,id); return; }
	int mid=(lc[id]+rc[id])/2;
	if(l<=mid) connect(lson[id],t,l,r);
	if(mid<r)  connect(rson[id],t,l,r);
	return;
}

这道题连完点之后再用Tarjan缩点成一个DAG然后建一个反图,跑一边拓扑,一边跑一遍跟新答案。
完整代码如下:

#include
#define LL long long
#define int long long
using namespace std;

const int maxn = 800010 , mod = 1000000007 , inf = 1000000007;
int n,ndnum=0,root;
int lson[maxn*4],rson[maxn*4],rc[maxn*4],lc[maxn*4];
int head[maxn*4],sc[maxn*4],cnt=0;
LL zha[maxn*4],pos[maxn*4];
int dfn[maxn*4],sta[maxn*4],top=0,low[maxn*4],tot=0;
int b[maxn*4],tim=0,out[maxn*4];
LL ans=0;
bool vis[maxn*4];

struct edge
{
	int to,pre;
}e[maxn*4];

struct scc1
{
	int to,pre;
}scc[maxn*4];

struct point
{
	int size;
	int l,r;
}po[maxn*4];

template<class T> void read(T &re)
{
	re=0;
	T sign=1;
	char tmp;
	while((tmp=getchar())&&(tmp<'0'||tmp>'9')) if(tmp=='-') sign=-1;
	re=tmp-'0';
	while((tmp=getchar())&&(tmp>='0'&&tmp<='9')) re=(re<<3)+(re<<1)+(tmp-'0');
	re*=sign;
}

inline void add(int x,int y)
{
	e[++cnt].pre=head[x];
	e[cnt].to=y;
	head[x]=cnt;
}

inline void add_scc(int x,int y)
{
	scc[++cnt].pre=sc[x];
	scc[cnt].to=y;
	sc[x]=cnt;
}

void build(int &t,int l,int r)
{
	if(l==r)
	{
		lc[l]=rc[l]=t=l;
		return;	
	}	
	t=++ndnum;
	lc[t]=l;
	rc[t]=r;
	int mid=(l+r)/2;
	build(lson[t],l,mid);
	build(rson[t],mid+1,r);
	add(t,lson[t]);
	add(t,rson[t]);
}

void connect(int id,int t,int l,int r)
{
	if(l<=lc[id]&&rc[id]<=r) { add(t,id); return; }
	int mid=(lc[id]+rc[id])/2;
	if(l<=mid) connect(lson[id],t,l,r);
	if(mid<r)  connect(rson[id],t,l,r);
	return;
}

int lower_l(int x)
{
	int l=1,r=n,mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(pos[mid]>=x)
            r=mid-1;
        else
            l=mid+1;
    }
    return l;
}

int lower_r(int x)
{
	int l=1,r=n,mid;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(pos[mid]>x)
            r=mid-1;
        else
            l=mid+1;
    }
    return l;
}

void Tarjan(int x)
{
	dfn[x]=low[x]=++tim;
	vis[x]=1;
	sta[++top]=x;
	for(int i=head[x];i;i=e[i].pre)
	{
		int v=e[i].to;
		if(!dfn[v])
		{
			Tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v])
			low[x]=min(low[x],dfn[v]);
	}
	if(dfn[x]==low[x])
	{
		tot++;int y;
		do
		{
			y=sta[top--];
			b[y]=tot;
			vis[y]=0;
			if(y>=1&&y<=n)
			{
				po[tot].l=min(lc[y],po[tot].l);
				po[tot].r=max(rc[y],po[tot].r);
			}
		}
		while(x!=y);
	}
}

queue<int >q;

void top_sort()
{
	for(int i=1;i<=tot;++i)
		if(!out[i]) q.push(i);
	while(q.size())
	{
		int x=q.front();q.pop();
		po[x].size=(LL)(po[x].r-po[x].l+1);
		for(int i=sc[x];i;i=scc[i].pre)
		{
			int v=scc[i].to;
			out[v]--;
			if(!out[v]) q.push(v);
			po[v].l=min(po[v].l,po[x].l);
			po[v].r=max(po[v].r,po[x].r);
		}
	}
}
signed main()
{
	read(n);
	ndnum=n;
	build(root,1,n);
	for(int i=1;i<=ndnum;++i) po[i].l=inf,po[i].r=-inf;
	for(int i=1;i<=n;++i) read(pos[i]),read(zha[i]);
	
	for(int i=1;i<=n;++i)
	{
		int l=lower_l(pos[i]-zha[i]),r=lower_r(pos[i]+zha[i])-1;
		connect(root,i,l,r);
	}
	for(int i=1;i<=ndnum;++i)
		if(!dfn[i])
			Tarjan(i);
	cnt=0;
	for(int x=1;x<=ndnum;++x)
	{
		for(int i=head[x];i;i=e[i].pre)
		{
			int v=e[i].to;
			if(b[x]!=b[v])
			{
				add_scc(b[v],b[x]);
				out[b[x]]++;
			}
		}
	}
	top_sort();
	int ans=0;
	for(int i=1;i<=n;++i)
		ans=(ans+(LL)i*po[b[i]].size)%mod;
	printf("%lld",ans);
	return 0;	
} 

你可能感兴趣的:(线段树,图论,Tarjan)