zoukankan      html  css  js  c++  java
  • 洛谷 P5025

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

    洛谷题目页面传送门

    题意见洛谷。

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

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

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

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

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

    时空复杂度都是(mathrm O!left(nlog n ight))。常数比较大,把vector邻接表改成链式前向星即可不开O2 AC。有一个奇怪的现象,如果用vector,我们不是要开(mathrm O(nlog 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
    ",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
    ",dp[i]);
    		(ans+=1ll*(dp_r[i]-dp_l[i]+1)*sum%mod)%=mod;
    	}
    	cout<<ans;
    	return 0;
    }
    
  • 相关阅读:
    文艺青年会看这本《迷局》么?
    看文艺青年怎么玩微信客户端
    Sublime Text有哪些使用技巧(转)
    C++ 关键字 explicit, export, mutable
    move语义和右值引用
    C++11 std::function用法
    function adapter(函数适配器)和迭代器适配器
    for_each()的返回值
    C++11的一些新特性
    setw和setfill控制输出间隔
  • 原文地址:https://www.cnblogs.com/ycx-akioi/p/Luogu-P5025.html
Copyright © 2011-2022 走看看