[NOIP2015提高组]运输计划

Posted Mrsrz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[NOIP2015提高组]运输计划相关的知识,希望对你有一定的参考价值。

题目:BZOJ4326、洛谷P2680、Vijos P1983、UOJ#150、codevs4632、codevs5440。

题目大意:有一棵带权树,有一些运输计划,第i个运输计划从ai到bi,耗时为ai到bi的距离,所有运输计划一起开始。现在可以把一条边权变成0,求最终运输计划最短要多少时间。

解题思路:标算是树剖,然而我并不会……

我的方法是LCA+二分+树上差分。

首先LCA,求出每个运输计划的长度,可一遍dfs求出每个节点到根的距离dist,则a到b的长度为dist[a]+dist[b]-(dist[lca(a,b)]<<1)。

接着二分答案,然后判断答案可行性。

对于每一个答案,我们要找的是所有长度大于当前答案的运输计划的公共边,因为只有所有长度大于当前答案的运输计划全部变成小于等于当前答案,当前答案才可行。

用树上差分(不懂请百度)。我们用s[i]记录i到它父亲这条边有多少计划经过。

对于每个运输计划,如果长度大于当前答案,我们给s[a]+1,s[b]+1,s[lca(a,b)]-2,因为我们要统计的是边,所以对于两个点,lca(a,b)对应的边实际是没有走到的,所以-2。

差分完后判断答案可行性即可。

我用倍增时间复杂度为$O(m\log_2 n+(n+m)\log_2 len)$,len为运输计划的最大长度。

但常数巨大,1s很容易被卡,因此有些地方还过不掉。例如我在UOJ上就被卡97。但在时限较宽或数据较弱的地方还是能AC的。

C++ Code:

%:pragma GCC optimize(2)
#include<cstdio>
#include<cctype>
#include<cstring>
using namespace std;
char buf[10700005];
int bufpos,n,m,head[300001],deep[300001],p[300001][21],dist[300001],s[300001],fa_edge[300001],mx,now;
struct edge{
	int to,dis,nxt;
}e[600003];
struct que{
	int a,b,len,LCA;
}f[300001];
inline int max(int a,int b){return(a>b)?a:b;}
inline int readint(){
	char c=buf[bufpos++];
	for(;!isdigit(c);c=buf[bufpos++]);
	int p=0;
	for(;isdigit(c);c=buf[bufpos++])p=(p<<3)+(p<<1)+(c^‘0‘);
	return p;
}
void dfs(int s){
	for(int i=head[s];i;i=e[i].nxt)
	if(!deep[e[i].to]){
		fa_edge[e[i].to]=i;
		deep[e[i].to]=deep[s]+1;
		p[e[i].to][0]=s;
		dist[e[i].to]=dist[s]+e[i].dis;
		dfs(e[i].to);
	}
}
int lca(int x,int y){
	if(deep[x]<deep[y])x^=y^=x^=y;
	int i;
	for(i=0;(1<<i)<=deep[x];++i);--i;
	for(int j=i;j>-1;--j)
	if(deep[p[x][j]]>=deep[y])x=p[x][j];
	if(x==y)return x;
	for(int j=i;j>-1;--j)
	if(p[x][j]!=-1&&p[x][j]!=p[y][j])x=p[x][j],y=p[y][j];
	return p[x][0];
}
void updata(int now){
	for(int i=head[now];i;i=e[i].nxt)
	if(deep[now]<deep[e[i].to]){
		updata(e[i].to);
		s[now]+=s[e[i].to];
	}
}
bool ok(int ans){
	memset(s,0,sizeof s);
	int gz=0;
	for(int i=1;i<=n;++i)
	if(f[i].len>ans){
		++gz;
		++s[f[i].a];
		++s[f[i].b];
		s[f[i].LCA]-=2;
	}
	updata(1);
	for(int i=1;i<=m;++i)
	if(s[i]==gz&&mx-ans<=e[fa_edge[i]].dis)return true;
	return false;
}
int main(){
	buf[fread(buf,1,10700000,stdin)]=bufpos=0;
	m=readint(),n=readint();
	for(int i=1;i<m;++i){
		int from=readint(),to=readint(),dis=readint();
		now=i<<1;
		e[now-1]=(edge){to,dis,head[from]};
		head[from]=now-1;
		e[now]=(edge){from,dis,head[to]};
		head[to]=now;
	}
	memset(p,-1,sizeof p);
	dist[1]=0;
	deep[1]=1;
	dfs(1);
	for(int j=1;(1<<j)<=m;++j)
	for(int i=1;i<=m;++i)
	if(p[i][j-1]!=-1)p[i][j]=p[p[i][j-1]][j-1];
	int l=0,r=0,mid;
	for(int i=1;i<=n;++i){
		f[i].a=readint();
		f[i].b=readint();
		f[i].LCA=lca(f[i].a,f[i].b);
		r=max(r,f[i].len=dist[f[i].a]+dist[f[i].b]-(dist[f[i].LCA]<<1));
	}
	mx=r;
	++r;
	while(l<r){
		mid=l+r>>1;
		if(ok(mid))r=mid;else
		l=mid+1;
	}
	printf("%d\n",r);
	return 0;
}

倍增时间复杂度比较高,我改成用Tarjan求LCA,速度瞬间变快,在UOJ上成功卡了过去。不过codevs4632仍然被卡。

以下为Tarjan代码。

C++ Code:

%:pragma GCC optimize(2)
#include<cstdio>
#include<cctype>
#include<cstring>
using namespace std;
char buf[10700005];
int bufpos,n,m,head[300001],deep[300001],dist[300001],s[300001],fa_edge[300001],mx,now,headq[300001],nq=0;
bool vis[300001];
int fa[300001];
struct edge{
	int to,dis,nxt;
}e[600003];
struct que{
	int a,b,len,LCA;
}f[300001];
struct Query{
    int same,nxt,to,num;
    bool flag;
}q[600003];
int find(int x){return(fa[x]==x)?x:(fa[x]=find(fa[x]));}
inline int max(int a,int b){return(a>b)?a:b;}
inline int readint(){
	char c=buf[bufpos++];
	for(;!isdigit(c);c=buf[bufpos++]);
	int p=0;
	for(;isdigit(c);c=buf[bufpos++])p=(p<<3)+(p<<1)+(c^‘0‘);
	return p;
}
void dfs(int s){
	for(int i=head[s];i;i=e[i].nxt)
	if(!deep[e[i].to]){
		fa_edge[e[i].to]=i;
		deep[e[i].to]=deep[s]+1;
		dist[e[i].to]=dist[s]+e[i].dis;
		dfs(e[i].to);
	}
}
void tarjan(int root){
    for(int i=head[root];i;i=e[i].nxt)
	if(deep[root]<deep[e[i].to]){
        tarjan(e[i].to);
        fa[e[i].to]=root;
        vis[e[i].to]=true;
    }
    for(int i=headq[root];i;i=q[i].nxt)
    if(vis[q[i].to]&&!q[i].flag){
        q[i].flag=q[q[i].same].flag=true;
        f[q[i].num].LCA=find(q[i].to);
    }
}
void updata(int now){
	for(int i=head[now];i;i=e[i].nxt)
	if(deep[now]<deep[e[i].to]){
		updata(e[i].to);
		s[now]+=s[e[i].to];
	}
}
bool ok(int ans){
	memset(s,0,sizeof s);
	int gz=0;
	for(int i=1;i<=n;++i)
	if(f[i].len>ans){
		++gz;
		++s[f[i].a];
		++s[f[i].b];
		s[f[i].LCA]-=2;
	}
	updata(1);
	for(int i=1;i<=m;++i)
	if(s[i]==gz&&mx-ans<=e[fa_edge[i]].dis)return true;
	return false;
}
int main(){
	buf[fread(buf,1,10700000,stdin)]=bufpos=0;
	m=readint(),n=readint();
	for(int i=1;i<m;++i){
		int from=readint(),to=readint(),dis=readint();
		now=i<<1;
		e[now-1]=(edge){to,dis,head[from]};
		head[from]=now-1;
		e[now]=(edge){from,dis,head[to]};
		head[to]=now;
		fa[i]=i;
	}
	fa[m]=m;
	deep[1]=1;
	dfs(1);
	int l=0,r=0,mid;
	for(int i=1;i<=n;++i){
		f[i].a=readint();
		f[i].b=readint();
		int& x=f[i].a,&y=f[i].b;
  		q[++nq].to=y;
    	q[nq].same=nq+1;
   		q[nq].num=i;
    	q[nq].nxt=headq[x];
    	headq[x]=nq;
    	q[++nq].to=x;
    	q[nq].same=nq-1;
    	q[nq].num=i;
    	q[nq].nxt=headq[y];
    	headq[y]=nq;
    	if(x==y)
    	q[nq].flag=q[nq-1].flag=true,f[i].LCA=x;
	}
	tarjan(1);
	for(int i=1;i<=n;++i)
	r=max(r,f[i].len=dist[f[i].a]+dist[f[i].b]-(dist[f[i].LCA]<<1));
	mx=r;
	++r;
	while(l<r){
		mid=l+r>>1;
		if(ok(mid))r=mid;else
		l=mid+1;
	}
	printf("%d\n",r);
	return 0;
}

 

以上是关于[NOIP2015提高组]运输计划的主要内容,如果未能解决你的问题,请参考以下文章

[NOIP2015 提高组] 运输计划

[NOIP2015] 提高组 洛谷P2680 运输计划

[NOIP2015 提高组] 运输计划 lca 正确做法

洛谷 P2680 [NOIP2015 提高组] 运输计划(二分,树上查分,树上倍增,LCA)

如何评价NOIP2015提高组复赛试题

AC日记——[NOIP2015]运输计划 cogs 2109