NOIP2015 Day2 T3 运输计划

即将参加NOIP2019的蒟蒻报道

    经过一夜的思考,终于将NOIP2015最难的题解决了,至此NOIP2015全部6题AC
  1. 运输计划
    (transport.cpp/c/pas)
    【问题描述】
    公元 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号星球。
    【输出格式】
    输出文件名为 transport.out。
    共 1 行,包含 1 个整数,表示小 P 的物流公司完成阶段性工作所需要的最短时间。
    【输入输出样例 1】
    transport.in transport.out
    6 3
    1 2 3
    1 6 4
    3 1 7
    4 3 6
    3 5 5
    3 6
    2 5
    4 5
    11
    见选手目录下的 transport/transport1.in 与 transport/transport1.ans
    【输入输出样例 1 说明】
    将第 1 条航道改造成虫洞:则三个计划耗时分别为:11、12、11,故需要花费的时
    间为 12。
    将第 2 条航道改造成虫洞:则三个计划耗时分别为:7、15、11,故需要花费的时
    间为 15。
    将第 3 条航道改造成虫洞:则三个计划耗时分别为:4、8、11,故需要花费的时间
    为 11。
    将第 4 条航道改造成虫洞:则三个计划耗时分别为:11、15、5,故需要花费的时
    间为 15。
    将第 5 条航道改造成虫洞:则三个计划耗时分别为:11、10、6,故需要花费的时
    间为 11。
    故将第 3 条或第 5 条航道改造成虫洞均可使得完成阶段性工作的耗时最短,需要花
    费的时间为 11。
    【样例输入输出 2】
    见选手目录下的 transport/transport2.in 与 transport/transport2.ans。
    【数据规模与约定】
    所有测试数据的范围和特点如下表所示
    测试点编号 n= m= 约定
    1 100 1
    2 100 第 i 条航道连接 i 号星球与 i+1 号星球
    3
    4 2000 1
    5 1000 1000
    6 2000 2000 第 i 条航道连接 i 号星球与 i+1 号星球
    7 3000 3000
    8 1000 1000
    9 2000 2000
    10 3000 3000
    11 80000 1
    12 100000
    13 70000 70000
    14 80000 80000 第 i 条航道连接 i 号星球与 i+1 号星球
    15 90000 90000
    16 100000 100000
    17 80000 80000
    18 90000 90000
    19 100000 100000
    20 300000 300000
    所有数据 1≤ai,bi,uj,vj≤n,0≤ti≤1000
    请注意常数因子带来的程序效率上的影响。

乍一看 m=1,有20分 LCA找到路径上最长的路,删掉即可。

再一看有链,又有20分,可以通过维护一个sum数组,将查询边长的时间复杂度降为O(1),暴力枚举删除每一条边即可

最后理性分析,我们将特殊情况扩展到一般情况,发现求解最大值最小,考虑使用二分,我们接着看如何判断解的可行性,我们可以发现,每次确定一个答案,我们需要找到所有路径长度超过这个答案的计划,找到其中一条公共边,保证这条公共边的权值是所有公共边中最大的那个,减掉路径后,所有超过这个答案的路径长度变短,如果有长度仍然超过的,那么这个答案不可行,否则可行。

那么应该怎样去找公共边呢?我们考虑树上差分,如果覆盖一次这个边,那么将temp[plan[i].start]++,temp[plan[i].end]++,temp[plan[i].lca]-=2,然后在最后从子节点开始累加,最后temp中存入的是每个结点覆盖的次数。找到temp[i]=超过答案的边的个数并且dis[i]-dis[fa[i]]>=ans,那么答案可行,否则不可行

所以代码如下:

#include <bits/stdc++.h>
using namespace std;
#define maxn 300005
int f[maxn][25],head[maxn],deep[maxn],num[maxn],dis[maxn],tot[maxn];
int cnt=0,n,m,summ=0;
struct node
{
	int to,next,w;
}e[maxn*2];
struct Node
{
	int start,end,lca,diss;
}plan[maxn];
inline int read()
{
	int w=1,s=0; char ch=getchar();
	while(ch<'0' || ch>'9'){if(ch=='-')w=-1; ch=getchar();}
	while(ch>='0' && ch<='9'){s=s*10+ch-'0'; ch=getchar();}
	return w*s;
}
void add(int x,int y,int v)
{
	cnt++;
	e[cnt].to=y;
	e[cnt].next=head[x];
	e[cnt].w=v;
	head[x]=cnt;
}
void dfs(int u,int fa,int d)
{
	summ++;
	deep[u]=deep[fa]+1;
	num[summ]=u;
	dis[u]=dis[fa]+d;
	f[u][0]=fa;
	for(int i=1;i<=22;i++) f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].next)
	{
		int v=e[i].to,dist=e[i].w;
		if(v==fa) continue;
		dfs(v,u,dist);
	}
}
int LCA(int x,int y)
{
	if(deep[x]<deep[y]) swap(x,y);
	for(int i=22;i>=0;i--)
	{
		if(deep[f[x][i]]>=deep[y]) x=f[x][i];
	}
	if(x==y) return x;
	for(int i=22;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i]; y=f[y][i];
		}
	}
	return f[x][0];
}
bool check(int mid)
{
	int s=0,ans=0;
	memset(tot,0,sizeof(tot));
	for(int i=1;i<=m;i++)
	{
		if(plan[i].diss>mid)
		{
			tot[plan[i].start]++; tot[plan[i].end]++; tot[plan[i].lca]-=2;
			ans=max(ans,plan[i].diss-mid);
			s++;
		}
	}
	if(s==0) return true;
	for(int i=n;i>=1;i--) tot[f[num[i]][0]]+=tot[num[i]]; 
	for(int i=1;i<=n;i++) if(tot[i]==s && dis[i]-dis[f[i][0]]>=ans) return true;
	return false;
}
int main()
{
	n=read(); m=read();
	int sum=0;
	for(int i=1;i<n;i++)
	{
		int a=read(),b=read(),c=read();
		add(a,b,c); add(b,a,c);
		sum+=c;
	}
	dfs(1,0,0);
	for(int i=1;i<=m;i++)
	{
		plan[i].start=read(); plan[i].end=read();
		plan[i].lca=LCA(plan[i].start,plan[i].end);
		plan[i].diss=dis[plan[i].start]+dis[plan[i].end]-2*dis[plan[i].lca];
	}
	int l=0,r=sum;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(check(mid)==true) r=mid-1;
		else 				 l=mid+1;
	}
	printf("%d",l);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值