[SNOI2017]炸弹(tarjan+DP+优化建图)

洛谷题目传送门

解题思路

嗯,我觉得看出是个有向图并不难
把每个炸弹向他能炸到的连一条边
然后缩点,那么每个炸弹一定能炸到他所属的强连通分量里的所有炸弹
缩完点后是一个DAG,那么对于新图上的每个点来说,他在DAG上能到达所有店都能炸到
并且我们能发现一个性质,一个炸弹能炸到的一定是连续的一段,所以我们建这个DAG的反图,进行DP,更新能炸到的左端点和右端点
但是数据有点大,所以一个一个连边就会T
一般情况,我们会选择线段树优化建图
但是众所周知,线段树优化建图细节很多,很难调
所以我们要另想一种方法优化
举个栗子
如果A能炸到B,我们记作A->B
如果按顺序有三个点A,B,C
A->C,B->C,A->B
那么A->C这条边是没有必要的,所以对于每个炸弹,我们只从左边第一个能炸到他的炸弹向他连一条边,右边同理,这个可以用单调栈维护

#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+7;
typedef long long LL;
const LL mod = 1e9+7;
struct node
{
	LL y,next;
}e[4*N],ec[4*N];
LL link[N],linkc[N],tc,t=0;
LL deg[N];
void add(LL x,LL y)
{
	t++;
	e[t].y=y;
	e[t].next=link[x];
	link[x]=t;
}
void addc(LL x,LL y)
{
	tc++;
	ec[tc].y=y;
	ec[tc].next=linkc[x];
	deg[y]++;
	linkc[x]=tc;
}
LL pos[N],sum=0,range[N],L[N],R[N];
LL n;
inline LL read()
{
	LL X=0; bool flag=1; char ch=getchar();
	while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();}
	while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();}
	if(flag) return X;
	return ~(X-1);
}
LL st[N],top,Lx[N],Rx[N];
LL low[N],dfn[N],num,cnt=0,bel[N];
bool ins[N];
vector<LL> scc[N];
void tarjan(LL x)
{
	dfn[x]=low[x]=++num;
	st[++top]=x;
	ins[x]=1;
	for(LL i=link[x];i;i=e[i].next)
	{
		LL y=e[i].y;
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]); 
		}
		else if(ins[y])
		low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x])
	{
		cnt++;
		LL y;
		do
		{
			y=st[top--];
			ins[y]=0;
			bel[y]=cnt;
			Lx[cnt]=min(Lx[cnt],y);
			Rx[cnt]=max(Rx[cnt],y);
			scc[cnt].push_back(y);
		}while(x!=y);
	}
}
queue<LL> q;

void topsort()
{
	for(LL i=1;i<=n;i++)
	if(deg[i]==0) q.push(i);
	while(!q.empty())
	{
		LL x=q.front();
		q.pop();
		for(LL i=linkc[x];i;i=ec[i].next)
		{
			LL y=ec[i].y;
			if(deg[y]<=0) continue;
			deg[y]--;
			Lx[y]=min(Lx[y],Lx[x]);
			Rx[y]=max(Rx[y],Rx[x]);
			if(deg[y]==0) q.push(y);
		}
	}
}
int main()
{
	n=read();
	for(LL i=1;i<=n;i++)
	{
		pos[i]=read();
		range[i]=read();
		L[i]=pos[i]-range[i];
		R[i]=pos[i]+range[i];
	}
	for(int i=1;i<=n;i++)
	{
		Rx[i]=-1e18-9;
		Lx[i]=1e18+9;
	}
	for(LL i=1;i<=n;i++)
	{
		while(top&&R[st[top]]<pos[i]) --top;
		if(top) add(st[top],i);
		while(top&&R[st[top]]<=R[i]) --top;
		st[++top]=i;
	}
	memset(st,0,sizeof(st));
	top=0;
	for(LL i=n;i>=1;i--)
	{
		while(top&&L[st[top]]>pos[i]) --top;
		if(top) add(st[top],i);
		while(top&&L[st[top]]>=L[i]) --top;
		st[++top]=i;
	}
	memset(st,0,sizeof(st));
	top=0;
	for(LL i=1;i<=n;i++)
	if(!dfn[i]) tarjan(i);
	for(LL x=1;x<=n;x++)
	{
		for(LL i=link[x];i;i=e[i].next)
		{
			LL y=e[i].y;
			if(bel[x]==bel[y]) continue; 
			addc(bel[y],bel[x]);
		}
	} 
	topsort();
	for(LL i=1;i<=n;i++)
	sum=(sum+i*(Rx[bel[i]]-Lx[bel[i]]+1)%mod)%mod;
	cout<<sum;
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值