洛谷 P2680 运输计划

45 篇文章 0 订阅
5 篇文章 0 订阅

题目背景

公元 2044 年,人类进入了宇宙纪元。

题目描述

L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之间,这 n-1 条航道连通了 L 国的所有星球。

小 P 掌管一家物流公司,该公司有很多个运输计划,每个运输计划形如:有一艘物

流飞船需要从 ui 号星球沿最快的宇航路径飞行到 vi 号星球去。显然,飞船驶过一条航道 是需要时间的,对于航道 j,任意飞船驶过它所花费的时间为 tj,并且任意两艘飞船之 间不会产生任何干扰。

为了鼓励科技创新,L 国国王同意小 P 的物流公司参与 L 国的航道建设,即允许小 P 把某一条航道改造成虫洞,飞船驶过虫洞不消耗时间。

在虫洞的建设完成前小 P 的物流公司就预接了 m 个运输计划。在虫洞建设完成后, 这 m 个运输计划会同时开始,所有飞船一起出发。当这 m 个运输计划都完成时,小 P 的 物流公司的阶段性工作就完成了。

如果小 P 可以自由选择将哪一条航道改造成虫洞,试求出小 P 的物流公司完成阶段 性工作所需要的最短时间是多少?

输入输出格式

输入格式:

输入文件名为 transport.in。

第一行包括两个正整数 n、m,表示 L 国中星球的数量及小 P 公司预接的运输计划的数量,星球从 1 到 n 编号。

接下来 n-1 行描述航道的建设情况,其中第 i 行包含三个整数 ai, bi 和 ti,表示第

i 条双向航道修建在 ai 与 bi 两个星球之间,任意飞船驶过它所花费的时间为 ti。

接下来 m 行描述运输计划的情况,其中第 j 行包含两个正整数 uj 和 vj,表示第 j个 运输计划是从 uj 号星球飞往 vj 号星球。

输出格式:

输出文件只包含一个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。

输入输出样例

输入样例#1:
6 3 
1 2 3 
1 6 4 
3 1 7 
4 3 6 
3 5 5 
3 6 
2 5 
4 5
输出样例#1:
11

说明

所有测试数据的范围和特点如下表所示


请注意常数因子带来的程序效率上的影响。


预处理出询问的lca,枚举在那条路建立虫洞,dfs暴力更新到根节点的距离,可以得50分。


#include<algorithm>
#include<iostream>
#include<cstdio>
using namespace std;
const int N=300005;
int n,m,cnt,ans=1e9+7,hd[N],dep[N],f[N][21],dis[N],ban;
struct edge
{
	int to,nxt,val,num;
}v[2*N];
struct node
{
	int x,y,lcaa;
}e[N];
void addedge(int x,int y,int z,int id)
{
	++cnt;
	v[cnt].to=y;
	v[cnt].nxt=hd[x];
	v[cnt].val=z;
	v[cnt].num=id;
	hd[x]=cnt;
}
void init(int u,int fa)
{
	f[u][0]=fa;
	dep[u]=dep[fa]+1;
	for(int i=hd[u];i;i=v[i].nxt)
		if(v[i].to!=fa)
			init(v[i].to,u);
}
int lca(int x,int y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=18;i>=0;i--)
		if(dep[f[x][i]]>=dep[y])
			x=f[x][i];
	if(x==y)
		return x;
	for(int i=18;i>=0;i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
void dfs(int u,int fa)
{
	for(int i=hd[u];i;i=v[i].nxt)
		if(v[i].to!=fa)
		{
			if(v[i].num!=ban)
				dis[v[i].to]=dis[u]+v[i].val;
			else
				dis[v[i].to]=dis[u];
			dfs(v[i].to,u);
		}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		addedge(x,y,z,i);
		addedge(y,x,z,i);
	}
	init(1,0);
	for(int i=1;i<=18;i++)
		for(int j=1;j<=n;j++)
			f[j][i]=f[f[j][i-1]][i-1];
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&e[i].x,&e[i].y);
		e[i].lcaa=lca(e[i].x,e[i].y);
	}
	for(int i=1;i<=n-1;i++)//删边 
	{
		ban=i;
		dfs(1,0);
		int mx=0;
		for(int j=1;j<=m;j++)
			mx=max(mx,dis[e[j].x]+dis[e[j].y]-2*dis[e[j].lcaa]);
		ans=min(ans,mx);
	}
	printf("%d\n",ans);
	return 0;
}

迷之二分答案,转变为可行性问题。
如果所有的边都比lim小于等于,一定可行,否则从两端点搜索到lca,将经过的边++,如果有一条边所有的计划都经过了,且mx-边权<=lim,就可行。80分。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=300005;
int n,m,cnt,hd[N],dep[N],f[N][21],dis[N],ac[N],val[N];
struct edge
{
	int to,nxt,num;
}v[2*N];
struct node
{
	int x,y,lca,dist;
}e[N];
void addedge(int x,int y,int id)
{
	++cnt;
	v[cnt].to=y;
	v[cnt].nxt=hd[x];
	v[cnt].num=id;
	hd[x]=cnt;
}
void init(int u,int fa)
{
	f[u][0]=fa;
	dep[u]=dep[fa]+1;
	for(int i=hd[u];i;i=v[i].nxt)
		if(v[i].to!=fa)
		{
			dis[v[i].to]=dis[u]+val[v[i].num];
			init(v[i].to,u);
		}
}
int getlca(int x,int y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	for(int i=17;i>=0;i--)
		if(dep[f[x][i]]>=dep[y])
			x=f[x][i];
	if(x==y)
		return x;
	for(int i=17;i>=0;i--)
		if(f[x][i]!=f[y][i])
			x=f[x][i],y=f[y][i];
	return f[x][0];
}
void dfs(int u,int ban)
{
	if(u==ban)
		return ;
	for(int i=hd[u];i;i=v[i].nxt)
		if(dep[v[i].to]<dep[u])
		{
			ac[v[i].num]++;
			dfs(v[i].to,ban);
		}
}
bool pd(int lim)
{
	memset(ac,0,sizeof(ac));
	int sum=0,mx=0;
	for(int i=1;i<=m;i++)
		if(e[i].dist>lim)
		{
			sum++;
			mx=max(mx,e[i].dist);
			dfs(e[i].x,e[i].lca);
			dfs(e[i].y,e[i].lca);
		}
	if(sum==0)
		return 1;
	for(int i=1;i<=n-1;i++)
		if(ac[i]==sum&&mx-val[i]<=lim)
			return 1;
	return 0;
}
int main()
{
	int l=0,r=0,mid;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++)
	{
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		addedge(x,y,i);
		addedge(y,x,i);
		val[i]=z;
	}
	init(1,0);
	for(int i=1;i<=17;i++)
		for(int j=1;j<=n;j++)
			f[j][i]=f[f[j][i-1]][i-1];
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&e[i].x,&e[i].y);
		e[i].lca=getlca(e[i].x,e[i].y);
		e[i].dist=dis[e[i].x]+dis[e[i].y]-2*dis[e[i].lca];
		r=max(r,e[i].dist);
	}
	while(l<r)
	{
		mid=(l+r)/2;
		if(!pd(mid))
			l=mid+1;
		else
			r=mid;
	}
	printf("%d\n",l);
	return 0;
}

想一想,每次都直接dfs太zz了,可以把它们存起来,从根节点只dfs一遍,就是差分(不懂),详见代码。(终于ac了)


#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=300005;
int n,m,cnt,hd[N],dep[N],f[N][21],dis[N],ac[N],val[N],hh[N];
struct edge
{
    int to,nxt,num;
}v[2*N];
struct node
{
    int x,y,lca,dist;
}e[N];
void addedge(int x,int y,int id)
{
    ++cnt;
    v[cnt].to=y;
    v[cnt].nxt=hd[x];
    v[cnt].num=id;
    hd[x]=cnt;
}
void init(int u,int fa)
{
    f[u][0]=fa;
    dep[u]=dep[fa]+1;
    for(int i=hd[u];i;i=v[i].nxt)
        if(v[i].to!=fa)
        {
            dis[v[i].to]=dis[u]+val[v[i].num];
            init(v[i].to,u);
        }
}
int getlca(int x,int y)
{
    if(dep[x]<dep[y])
        swap(x,y);
    for(int i=17;i>=0;i--)
        if(dep[f[x][i]]>=dep[y])
            x=f[x][i];
    if(x==y)
        return x;
    for(int i=17;i>=0;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    return f[x][0];
}
int dfs(int u,int fa)
{
    int t=0;
    for(int i=hd[u];i;i=v[i].nxt)
        if(v[i].to!=fa)
        {
            int x=dfs(v[i].to,u);
            ac[v[i].num]+=x;
            t+=x;
        }
    return t+hh[u];
}
bool pd(int lim)
{
    memset(ac,0,sizeof(ac));
    memset(hh,0,sizeof(hh));
    int sum=0,mx=0;
    for(int i=1;i<=m;i++)
        if(e[i].dist>lim)
        {
            sum++;
            mx=max(mx,e[i].dist);
            hh[e[i].x]++;
            hh[e[i].y]++;
            hh[e[i].lca]-=2;
        }
    if(sum==0)
        return 1;
    dfs(1,0);
    for(int i=1;i<=n-1;i++)
        if(ac[i]==sum&&mx-val[i]<=lim)
            return 1;
    return 0;
}
int main()
{
    int l=0,r=0,mid;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n-1;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        addedge(x,y,i);
        addedge(y,x,i);
        val[i]=z;
    }
    init(1,0);
    for(int i=1;i<=17;i++)
        for(int j=1;j<=n;j++)
            f[j][i]=f[f[j][i-1]][i-1];
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&e[i].x,&e[i].y);
        e[i].lca=getlca(e[i].x,e[i].y);
        e[i].dist=dis[e[i].x]+dis[e[i].y]-2*dis[e[i].lca];
        r=max(r,e[i].dist);
    }
    while(l<r)
    {
        mid=(l+r)/2;
        if(!pd(mid))
            l=mid+1;
        else
            r=mid;
    }
    printf("%d\n",l);
    return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值