洛谷 P5025 - 炸弹

洛谷又关发题解入口了…………………………

洛谷题目页面传送门

题意见洛谷。

不难想到建图,将每个炸弹连向所有它能炸到的炸弹,然后在这个有向图上处理。

先来考虑怎么处理。不难发现,每个SCC内的节点的最终能炸到的炸弹集合是相等的。于是我们跑一遍Tarjan,然后缩点。这时候变成了一个DAG,我们只需要求出每个SCC对应的炸弹集合大小,即它能够到达的非虚拟节点节点的数量即可。定义每个SCC的权值为它内部的非虚拟节点节点的数量,那么要求的就是缩点之后每个点能够到达的所有点的点权之和。考虑DP,\(dp_i=\sum\limits_{(i,j)\in E}dp_j\)。然后交到洛谷里,WA了前两个点;交到LOJ,AC。wtf??

看了神鱼的题解才醒悟过来,这样DP会算重(原数据太水了,洛谷加了hack数据)。根据她的题解,上面说的那个问题是个世纪难题。那怎么办呢?不难发现这里有特殊性质:每个炸弹最终能炸到的炸弹集合是个区间!这个证明实在是太简单了。于是我们可以维护每个SCC能到达的区间(的左端点和右端点),然后DP求这两个端点即可。这样不是\(\sum\)了,而是\(\min/\max\)了,就不存在重不重的问题了。

于是时间复杂度与边数成正比。

接下来考虑如何建图使得边数比较小。你可能会说,这也太套路了……线段树优化即可。由于这里是单点连向区间,只需要维护一棵虚拟节点线段树。

时空复杂度都是\(\mathrm O\!\left(n\log n\right)\)。常数比较大,把vector邻接表改成链式前向星即可不开O2 AC。有一个奇怪的现象,如果用vector,我们不是要开\(\mathrm O(n\log n)\)vector嘛,这样会导致机子很卡,输出答案之后还要停一会儿才结束程序。可能是因为vector空间实在太大了。于是就有了一个经验:下次遇到点数/边数比较大的图论问题,尽量用链式前向星。说起来,今年省选Day2T2也是这个问题,如果改成链式前向星大概率就AC了,可惜现场我想当然了,以为linux虚拟机里跑过就可以了,就懒得改了。当时几天后发现这个,后悔死………………

代码:

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
typedef long long ll;
const int inf=0x3f3f3f3f,mod=1000000007;
void read(ll &x){
	x=0;char c=getchar();bool ne=false;
	while(!isdigit(c))ne|=c=='-',c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
	if(ne)x=-x;
}
const int N=500000,NOW=N<<2,M=N*20;
int n;
struct bomb{ll x,r;}a[N+1];
bool operator<(bomb x,bomb y){return x.x<y.x;}
int now;
struct addedge{
	int sz,head[NOW+1],nxt[M+1],val[M+1];
	void init(){sz=0;memset(head,0,sizeof(head));}
	void ae(int x,int v){
		nxt[++sz]=head[x];val[sz]=v;head[x]=sz;
	}
}nei;
struct segtree{
	struct node{int l,r,nd;}nd[N<<2];
	#define l(p) nd[p].l
	#define r(p) nd[p].r
	#define nd(p) nd[p].nd
	void bld(int l=1,int r=n,int p=1){
		l(p)=l;r(p)=r;
		if(l==r)return nd(p)=l,void();
		nd(p)=++now;
		int mid=l+r>>1;
		bld(l,mid,p<<1);bld(mid+1,r,p<<1|1);
		nei.ae(nd(p),nd(p<<1));nei.ae(nd(p),nd(p<<1|1));
	}
	void init(){bld();}
	void ae(int l,int r,int v,int p=1){
		if(l<=l(p)&&r>=r(p))return nei.ae(v,nd(p)),void();
		int mid=l(p)+r(p)>>1;
		if(l<=mid)ae(l,r,v,p<<1);
		if(r>mid)ae(l,r,v,p<<1|1);
	} 
}segt;
int dfn[NOW+1],low[NOW+1],nowdfn;
int stk[NOW],top;
bool ins[NOW+1];
vector<vector<int> > scc;
int cid[NOW+1];
void dfs(int x){
	dfn[x]=low[x]=++nowdfn;
	ins[stk[top++]=x]=true;
	for(int i=nei.head[x];i;i=nei.nxt[i]){
		int y=nei.val[i];
		if(!dfn[y])dfs(y),low[x]=min(low[x],low[y]);
		else if(ins[y])low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		scc.pb(vector<int>());
		while(true){
			int y=stk[--top];
			ins[y]=false;
			scc.back().pb(y);cid[y]=scc.size()-1;
			if(y==x)break;
		}
	}
}
addedge cnei;
int dp_l[NOW],dp_r[NOW];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)read(a[i].x),read(a[i].r);
	now=n;
	nei.init();
	segt.init();
	for(int i=1;i<=n;i++){//建图 
		int l=lower_bound(a+1,a+n+1,bomb({a[i].x-a[i].r,0}))-a,r=upper_bound(a+1,a+n+1,bomb({a[i].x+a[i].r,0}))-1-a;
		segt.ae(l,r,i);
	}
	for(int i=1;i<=now;i++)if(!dfn[i])dfs(i);//Tarjan
	cnei.init();
	for(int i=1;i<=now;i++)for(int j=nei.head[i];j;j=nei.nxt[j]){//缩点 
		int x=nei.val[j];
		if(cid[i]!=cid[x])cnei.ae(cid[i],cid[x]);
	}
	int ans=0;
	for(int i=0;i<scc.size();i++){//DP 
		dp_l[i]=inf;dp_r[i]=-inf;
		vector<int> &v=scc[i];
//		printf("scc#%d=",i);for(int j=0;j<v.size();j++)cout<<v[j]<<" ";puts("");
		int sum=0;
		for(int j=0;j<v.size();j++)if(v[j]<=n)
			dp_l[i]=min(dp_l[i],v[j]),dp_r[i]=max(dp_r[i],v[j]),(sum+=v[j])%=mod;
		for(int j=cnei.head[i];j;j=cnei.nxt[j]){
			int x=cnei.val[j];
			dp_l[i]=min(dp_l[i],dp_l[x]);dp_r[i]=max(dp_r[i],dp_r[x]);
		}
//		printf("dp=%d\n",dp[i]);
		(ans+=1ll*(dp_r[i]-dp_l[i]+1)*sum%mod)%=mod;
	}
	cout<<ans;
	return 0;
}

然而这题真的真的一脸有线性复杂度做法的样子。因为在DP出错然后发现性质的那个时候,就已经暗示了这题有特殊性质,不是一般的区间连边,有可能能做到线性。

想连出边没什么前途。不妨反过来想,盯着一个点的连向它的入边(其实在算贡献的题目中,这个思想就是换一个贡献体)。注意到,能向它连入边的点的爆炸范围都能覆盖它。而对于它左边,显然那些能连入边的点都覆盖最右边那个点;右边类似。于是我们只需要对于每个点,让左右两侧最靠近它的能连向它的点连向它即可,这样正确性可以用“能到达”的传递性证。

现在边数复杂度\(\mathrm O(n)\)了,我们想努力把连边也做到\(\mathrm O(n)\),这样总时空复杂度就是\(\mathrm O(n)\)了。难点在于如何快速找到最靠近的能连向它的点。以左边为例,可以从左往右扫描,任意时刻显然选越后被扫描到的越好。而每个点的爆炸范围又是一个区间,一旦不能被它爆炸到,以后的点都不能了。很自然地想到单调栈。

代码(在之前的代码上魔改的):

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
typedef long long ll;
const int inf=0x3f3f3f3f,mod=1000000007;
void read(ll &x){
	x=0;char c=getchar();bool ne=false;
	while(!isdigit(c))ne|=c=='-',c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
	if(ne)x=-x;
}
const int N=500000,M=N<<1;
int n;
struct bomb{ll x,r;}a[N+1];
bool operator<(bomb x,bomb y){return x.x<y.x;}
struct addedge{
	int sz,head[N+1],nxt[M+1],val[M+1];
	void init(){sz=0;memset(head,0,sizeof(head));}
	void ae(int x,int v){
		nxt[++sz]=head[x];val[sz]=v;head[x]=sz;
	}
}nei;
int dfn[N+1],low[N+1],nowdfn;
int stk[N],top;
bool ins[N+1];
vector<vector<int> > scc;
int cid[N+1];
void dfs(int x){
	dfn[x]=low[x]=++nowdfn;
	ins[stk[top++]=x]=true;
	for(int i=nei.head[x];i;i=nei.nxt[i]){
		int y=nei.val[i];
		if(!dfn[y])dfs(y),low[x]=min(low[x],low[y]);
		else if(ins[y])low[x]=min(low[x],dfn[y]);
	}
	if(dfn[x]==low[x]){
		scc.pb(vector<int>());
		while(true){
			int y=stk[--top];
			ins[y]=false;
			scc.back().pb(y);cid[y]=scc.size()-1;
			if(y==x)break;
		}
	}
}
addedge cnei;
int dp_l[N],dp_r[N];
int main(){
	cin>>n;
	for(int i=1;i<=n;i++)read(a[i].x),read(a[i].r);
	nei.init();
	for(int i=1;i<=n;i++){//连边 
		while(top&&a[stk[top-1]].x+a[stk[top-1]].r<a[i].x)top--;
		if(top)nei.ae(stk[top-1],i);
		stk[top++]=i;
	}
	top=0;
	for(int i=n;i;i--){//连边 
		while(top&&a[stk[top-1]].x-a[stk[top-1]].r>a[i].x)top--;
		if(top)nei.ae(stk[top-1],i);
		stk[top++]=i;
	}
	top=0;
	for(int i=1;i<=n;i++)if(!dfn[i])dfs(i);//Tarjan 
	cnei.init();
	for(int i=1;i<=n;i++)for(int j=nei.head[i];j;j=nei.nxt[j]){//缩点 
		int x=nei.val[j];
		if(cid[i]!=cid[x])cnei.ae(cid[i],cid[x]);
	}
	int ans=0;
	for(int i=0;i<scc.size();i++){//DP 
		dp_l[i]=inf;dp_r[i]=-inf;
		vector<int> &v=scc[i];
//		printf("scc#%d=",i);for(int j=0;j<v.size();j++)cout<<v[j]<<" ";puts("");
		int sum=0;
		for(int j=0;j<v.size();j++)
			dp_l[i]=min(dp_l[i],v[j]),dp_r[i]=max(dp_r[i],v[j]),(sum+=v[j])%=mod;
		for(int j=cnei.head[i];j;j=cnei.nxt[j]){
			int x=cnei.val[j];
			dp_l[i]=min(dp_l[i],dp_l[x]);dp_r[i]=max(dp_r[i],dp_r[x]);
		}
//		printf("dp=%d\n",dp[i]);
		(ans+=1ll*(dp_r[i]-dp_l[i]+1)*sum%mod)%=mod;
	}
	cout<<ans;
	return 0;
}
posted @ 2020-08-04 09:46  ycx060617  阅读(232)  评论(0编辑  收藏  举报