出题人:Alicebuju,cgold,Leporidae
话说天下大事分久必合合久必分,今天下又三分,为了避
免生灵涂炭蜀、吴、魏三国决定用考试的方式决定霸主的
地位。汉献帝刘协不甘被曹操囚禁暗中派人偷偷混入考场
企图赢得霸主地位。而蜀、吴、魏三国君主也都是雄才大
略之辈,暗地里搜寻能人异士替他们作弊,于是一场没有
硝烟的战争正悄悄展开……
Problem 1 :
题目来源:http://www.lydsy.com/JudgeOnline/problem.php?id=1821
题目描述
为了应付考试,孙权决定请周瑜来帮忙,众所周知,吴国在长江南岸,有着得天独厚的地理条件,周瑜除了能把水军训练的井井有条,而且作弊方面也是一把好手。这天恰逢九星连珠,在我国的第一长河中,出现了一只可以给人带来好运的虾米,为了提高孙权的胜率,周瑜决定去寻找这只虾米,周瑜有n条船,每条船各有一个坐标,但在汉献帝刘协的干扰下,船无法移动,于是周瑜施展禁术,将船分为k个队伍,解除封印,但成功率与最近的两个队伍之间的距离有关。定义:两个队伍的距离为不在同一个队伍中的最近的两只船之间的距离。由于这种禁术的不确定性,刘协想知道,怎样划分船可以使两个队伍的距离最大,最大为多少(保留两位小数)。
输入描述
第一行包含两个整数N和K(1< K < = N),分别代表了船的数量和划分队伍的数量。
接下来N行,每行包含两个正整数x,y,描述了一只船的坐标。
输出描述
输出一行,为最近的两个队伍的距离,精确到小数点后两位。
样例输入
4 2
0 0
0 1
1 1
1 0
样例输出
1.00
数据范围
对于30%的数据 : 1<=N<=10, 1< K < = N,0 < =x, y < =10
对于100%的数据 : 1< = N< = 1000, 1< K < = N ,0 < =x, y < =1000
思路
首先划分过程中不要求也不可能平均地分配
将所有点划分成k和集合,可以看作把n个独立的集合合并至只剩下k个集合的过程。每把两个集合连边合并视为同一集合,他们之间的边就不被记录在不同集合的距离当中。所以,集合内的边一定不可能成为答案。而连边的过程就相当于扔掉了两个端点所在集合内部的任意两点间的边,也就是说,若之后找到的两个点在同一集合内,他们的距离是对答案是没有贡献的,那么我们也就不再连边。
我们假定求得集合外的最短边(记为x),为了使x成立,应该把一切边权小于x的边扔进集合内,即使这些边不可能成为答案。我们发现x的边权越大,扔进集合内部的边就会越多,那么队伍的数量就会越少,很明显是单调的。这样就可以二分了。
方案一:
将任意两点间的距离(边)处理出来,二分最短边的长度,这一步可以二分边权或二分边的下标。每次check时把所有边权 小于(1)(或小于等于(2)) mid的点对合并到同一个集合里面,用并查集来维护。然后统计集合的数量。当集合数量小于规定的k时,说明距离太大,那么就缩小距离,若不然(大于等于),增大距离。这样就能求出答案了。
这里有两种写法:1.二分最大的最小距离即答案。2.二分小于答案的前一条边。本质上并无差异。
下面的写法为第二种
代码
//二分边权
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,k,tot;
int fa[1000010];
bool h[1000010];
double l,r;
struct point
{
double x,y;
}p[2000010];
struct edge
{
int u,v;//没有起点终点之分
double w;
}ls[2000010];
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int check(double s)//传参double啊,不要和全局变量重名啊
{
memset(h,0,sizeof(h));
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=tot;i++)
{
if(ls[i].w<=s)
{
int u=find(ls[i].u);
int v=find(ls[i].v);
if(u!=v)
{
fa[u]=v;
}
}
}
int cnt=0;
for(int i=1;i<=n;i++)
{
if(!h[find(i)])
{
h[find(i)]=1;
cnt++;
}
}
if(cnt>=k)
return 1;
else return 0;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
for(int i=1;i<=n;i++)//预处理全部距离
{
for(int j=i+1;j<=n;j++)
{
ls[++tot].w=sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));
ls[tot].u=i;
ls[tot].v=j;
}
}
l=0;
r=101000;
while(l+0.0001<r)//精度多两位
{
double mid=(l+r)/2.0;//c++double不可右移
if(check(mid))
l=mid;
else r=mid;//队伍太少-->距离太大-->缩小距离
}
printf("%.2lf",r);
return 0;
}
//二分边的下标
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,k,tot,l,r,sum;
int fa[1000010];
bool h[1000010];
struct point
{
double x,y;
}p[1000010];
struct edge
{
int u,v;//没有起点终点之分
double w;
}ls[1000010];
bool cmp(edge a,edge b)
{
return a.w<b.w;
}
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int check(int s)
{
for(int i=1;i<=n;i++)
fa[i]=i,h[i]=0;
sum=0;
for(int i=1;i<=s;i++)//经过排序,直接连接即可
{
int u=find(ls[i].u);
int v=find(ls[i].v);
if(u!=v)
{
fa[u]=v;
}
}
sum=0;
for(int i=1;i<=n;i++)
{
if(!h[find(i)])
{
h[find(i)]=1;
sum++;
}
}
if(sum>=k)
return 1;
else return 0;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
ls[++tot].w=sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));//计算距离
ls[tot].u=i;
ls[tot].v=j;
}
}
sort(ls+1,ls+tot+1,cmp);
l=0;
r=tot+1;
while(l+1<r)
{
int mid=(l+r)>>1;
if(check(mid))
l=mid;
else r=mid;
}
printf("%.2lf",ls[l+1].w);//r
return 0;
}
方案二:
在不连边的情况下,那么答案一定是所有的边中最小的边。我们把这条最小的边连接,此时答案,又是所有剩余的边中最小的边,然后我们再进行连接。我们可以建立一棵最小生成树,来保证每次连边都是不在同一个集合内的两点且长度为当前最小,直到连接了n-k+1条边,那么这条边的边权就是答案。
n-k是为了保证能够建成k个集合(已知n-1条边是一棵树,若删去一条边–>n-2是两棵树……那么n-k就是k棵树)。+1的原因是为了保证当前求的这一条边是集合外的边。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,k,tot,l,r,cnt;
int fa[1000010],rs[1000010];
struct point
{
double x,y;
}p[1000010];
struct edge
{
int u,v;//没有起点终点之分
double w;
}ls[1000010];
bool cmp(int a,int b)
{
return ls[a].w<ls[b].w;
}
int find(int x)
{
return fa[x]==x?x:fa[x]=find(fa[x]);
}
void Kruskal()
{
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=tot;i++)
rs[i]=i;
sort(rs+1,rs+tot+1,cmp);
for(int i=1;i<=tot;i++)
{
int e=rs[i];
int u=find(ls[e].u);
int v=find(ls[e].v);
if(u!=v)
{
fa[u]=v;
cnt++;
if(cnt==n-k+1)
{
printf("%.2lf",ls[e].w);
break;
}
}
}
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%lf%lf",&p[i].x,&p[i].y);
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
ls[++tot].w=sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));
ls[tot].u=i;
ls[tot].v=j;
}
}
Kruskal();
return 0;
}
应该认真分析题设条件的关系求解。
Problem 2 :
题目来源:http://codevs.cn/problem/5251/
题目描述
刘备请来了“千古人龙”诸葛亮,而最近诸葛亮又在八卦阵的基础扩展出了复式八卦阵,可以在考试开封前30min知道考试的内容。
叠加规则如下:
首先有一个能量值为 n 的母盘(占高度),母盘上可放置一个能量值为a1且能量值不超过 n/k 的八卦阵,在八卦阵a1上又可放置一个能量值为 a2且不超过 a1/k的八卦阵,其中k为刘备的幸运数字,很显然能量值不能为负,且为了保证作弊效果最好,会在无法进一步扩展时才会停止,即最上方的八卦阵上无法叠加任何八卦阵时才记做一个完整的复式八卦阵,有了诸葛亮的帮助,刘备一定可以考取满分,但汉献帝刘协不甘如此,但手下无可用之才于是找到了会编程的你,让你算出诸葛亮所办八卦阵的数量对p取模的值以及最高的复式八卦阵的层数,因为这个值可以干扰复式八卦阵的运行。无奈诸葛亮的复式八卦阵可以算出自己的八卦阵是否被算出,若发现自己的复式八卦阵被算出则会更换母盘,并改变刘备的气运即改变刘备的幸运数字 k ,但由于诸葛亮过于自信,他只会对自己的复式八卦阵有没有被算出占卜T次(即会有T组数据)。
输入描述
输入数据的第一行包含两个正整数T、 p, T、 p的意义如题目所述。
后面跟着T组数据,每组数据仅一行,包含了一个正整数n和k,意义如题目所述
输出描述
输出数据一共n行,每行两个整数,表示答案对p取模后的值。
样例输入
2 2
6 2
20 3
样例输出
1 1
1 1
数据范围
对于10%的数据 : p = 2
对于100%的数据 :T<= 20,n <= 10^10, p <= 10的9次方 , k <= 10的6次方。
思路
对于高度贪心求解,每次尽量放最大的使叠放层数尽可能多。
而对于方案数。
方案一:递推
这也是我在考场的方案。分析叠放方案如下图:
可以发现,对于n为100,k为3来说,第二层(第一层为100)可以放置的方案有1 - 33的数字,对于小于k的数字,只可能叠放1层。对于大于等于k的数字,其叠放方案包含了其下一层可叠放的数字的方案(如18所示,下一层的方案包含有1 - 6的方案)。那么建立数组cnt,表示当前数可扩展出的答案数量(cnt[18]=7)。sum表示数量的前缀和。对于小于k的数字cnt = 1,而对于大于等于k的数字,不难看出其cnt=当前数i/k得到的数的前缀和。那么从前向后递推,中间过程取模即可。
注意判断k>n时,母盘本身也算作一种方案。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define RI register int
using namespace std;
typedef long long ll;
ll t,p,n,k,tp,tmp,h;
ll cnt[10000010],sum[10000010];//当前数可扩展出的答案数量,数量前缀和%k
int main()
{
scanf("%lld%lld",&t,&p);
for(RI i=1;i<=t;i++)
{
scanf("%lld%lld",&n,&k);
tp=n/k;
for(RI i=1;i<=tp;i++)
{
if(i<k)
{
cnt[i]=1;
sum[i]=(sum[i-1]+cnt[i])%p;
}
else
{
tmp=i/k;
cnt[i]=sum[tmp]%p;
sum[i]=(sum[i-1]+cnt[i])%p;
}
}
h=0;
while((n/k)>0)
{
h=(h+1)%p;
n/=k;
}
if(!tp)
sum[tp]=1;
printf("%lld %lld\n",sum[tp],(h+1)%p);
}
return 0;
}
方案二:DP
其实是没什么差别的…只不出题人把相同的内容写了一个DP写法而已。
通常意义下,递推是用来解决计数(如此题方案数)和可行性问题
而DP则用来解决最优化问题。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define RI register int
using namespace std;
typedef long long ll;
ll t,p,n,ns,k,h;
ll s[10000010],f[10000010];//s[i]以不大于i为底的母盘所达到的方案数之和,f[i]以i为底的方案数
int main()
{
scanf("%lld%lld",&t,&p);
for(RI i=1;i<=t;i++)
{
scanf("%lld%lld",&n,&k);
ns=n;
s[0]=s[1]=f[1]=1;
for(int i=2;i<=n/k;i++)
f[i]=s[i/k],s[i]=(s[i-1]+f[i])%p;
h=0;
while((n/k)>0)
{
h=(h+1)%p;
n/=k;
}
printf("%lld %lld\n",s[ns/k],(h+1)%p);
}
return 0;
}
Problem 3 :
题目来源:http://codevs.cn/problem/1684/
题目描述
曹操决定考试时找“鬼才”郭嘉来帮忙作弊,可他突然想起这时郭嘉好像真的成了鬼了,(众所周知,郭嘉死的早)于是干脆让郭嘉在考试时给他托梦,(他负责睡觉^(* ̄(oo) ̄)^)由于曹操睡觉打呼的声音吵到了别人,所以天道将他丢入了了梦之深渊,深渊的深度为D,而且在这里几乎任何东西都会很快被吞噬,幸好曹操有诸侯称号护体,暂时不会被吞噬,可称号只能帮他维持10s的时间。于是郭嘉从地狱带来了深渊币,并会在约定好的时间将深渊币投入深渊。曹操可以用深渊币回复称号能力(无上限)或者转化成可以存在于深渊的天梯,但深渊币也不尽相同(即转化的天梯高度和恢复的能量可能不同),若曹操与郭嘉会和将一定能考满分,而曹操一定会采取最佳策略,汉献帝刘协想知道曹操从梦中出来的时间,以便破坏这次见面,但由于手下无可用之才,于是找到了你……
输入描述
第一行为2个整数, D 和 G (1 <= G <= 100), G为被投入深渊的深渊币的数量。
第二到第G+1行每行包括3个整数: T (0 < T <= 1000),表示深渊币被投进井中的时间;F (1 <= F <= 30),表示该深渊币能恢复曹操称号能力的时间;和 H (1 <= H <= 25),该深渊币转化成的天梯的高度。
输出描述
如果曹操可以走出深渊,输出一个整数表示最早什么时候可以走出;否则输出曹操最长可以存活多长时间。
样例输入
20 4
5 4 9
9 3 2
12 6 10
13 1 1
样例输出
13
数据范围
见输入描述
思路
对于某一时间是否存活的判断,我们并不需要每次把时间都减去这些给出的能量,只需要判断总能量与时间大小关系就知道曹操是否会被吞噬。
既然不需要减去能量,我们只需要记录到达某一高度的最大能量值就可以了。
然后,剩余条件就转化为了一个背包问题(01背包的变形):
1.高度就是背包的容积,能量就是背包的价值。
2.梯子的高度是一定的,让高度一定时体力最大,只有某高度的体力大于下一深渊币的投放时刻时,才能更新。dp[j]>=l[i].t
dp[j]表示走了j高度可以达到的最长时间。
注意这道题中并没有保证数据按时间顺序给出,要先把深渊币按照时间排一遍序。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int d,g;
int dp[100010];
struct coin
{
int t,f,h;
}l[100010];
bool cmp(coin a,coin b)
{
return a.t<b.t;
}
int main()
{
scanf("%d%d",&d,&g);
for(int i=1;i<=g;i++)
scanf("%d%d%d",&l[i].t,&l[i].f,&l[i].h);
sort(l+1,l+g+1,cmp);//按时间排序
if(l[1].t>10)//特判
{
printf("0");
return 0;
}
dp[0]=10;//初始化
for(int i=1;i<=g;i++)
{
for(int j=d;j>=0;j--)//滚动数组优化
{
if(dp[j]>=l[i].t)//如果曹操不会被吞噬
{
if((j+l[i].h)>=d)//在某一时间可以逃出深渊
{
printf("%d",l[i].t);
return 0;
}//给状态方程
dp[j+l[i].h]=max(dp[j+l[i].h],dp[j]);//更新用此币搭建梯子的状态,此时的选择为搭建梯子
dp[j]+=l[i].f;//用此币增加能量
//两种状态可同时存在不相冲突,都用于可更新之后的状态
}
}
}
printf("%d",dp[0]);//不能逃出深渊的存活时间
return 0;
}
Problem 4 :
题目来源:http://codevs.cn/problem/5440/
题目描述
由于刘协阻止了三国的作弊计划,三位雄才大略的君主不约而同地爆了0,刘协想要回收散落在各地的龙气,以便进一步实现自己的目的。于是刘协搞到了一张世界地图,其中上面有m条龙气,为了尽可能地提高效率,并且不被诸侯们发现,他希望尽可能少地经过县城,但刘协还是找到了n个不可避开的县城(每两个点只有一条路径)及各点路径的长度。并且刘协知道,先帝曾给他留下了一台22世纪的任意门,可以无视距离。但是由于各种原因这个任意门流落到了民间(在这n个县城之中)。并因为年久失修,任意门只能转送到一个与原先这个点相连(即本来就有一条路) 的点。由于刘协的士兵散落在世界各地及通信的不便,每个士兵只知道一个龙气的位置(所有士兵知道的龙气视为不同龙气,即使龙气在同一县城)。刘协想知道任意门在任意位置时,自己派出的所有士兵都到达有龙气的位置,需要的最短的最大距离是多少。
输入描述
第一行包括两个正整数 n,m,表示县城的数量及有龙气的县城的数量,
县城从 1 到 n 编号。接下来 n−1 行,其中第 i 行包含三个整数 ai,bi 和 ci,表示从ai到bi的距离为ci数据保证 1≤ai,bi≤n 且 0≤ci≤1000。
接下来 m 行,其中第 j 行包含两个正整数 uj 和 vj,表示第 j 个士兵的初
始位置,和他知道的龙气的位置。数据保证 1≤ui,vi≤n
输出描述
输出文件只包含一个整数,表示最短距离。
样例输入
6 3
1 2 3
1 6 4
3 1 7
4 3 6
3 5 5
3 6 2
5 4 5
样例输出
11
数据范围
见附件
思路
NOIP 2015 运输计划原题
官方正解:Tarjin 离线求 lca + 二分 + 树上差分
Tarjin 求 lca 不会写….也可以:树链剖分(可换为倍增) 求 lca + 二分 + 树上差分 + dfs序
首先明确这是个卡常题,但并非单纯卡常就可以过。
树链剖分 + 线段树因为时间复杂度为O(mlog^3n)——m条路径,线段树求区间和logn,树剖跳链logn,二分logn只能过40%-50% ….还不如去打暴力和偏分。
Tarjin 算法求lca为O(n),就是在卡常。
树上差分可省去线段树的复杂度,但每次dfs去求前缀和的话,递归过程太慢会TLE…虽然都是O(n)。所以处理树上信息时顺便求出dfs序来求前缀和避免递归,也可以使用拓扑排序,原理相同。
这里介绍第二种写法:
首先分析题目可知,我们要删的边一定在最长路线上(1)。其次,要求最大化最小值,考虑二分最大路径长度。对于二分得出的mid,为删边后的最大路径长度,则:
1.所有大于mid的路线都必须删去一条边,使之小于或等于mid。
2.总共只能删一条边(2)。
若以上条件(1)(2)都可以满足,则mid为合法答案。
我们找到了所有大于mid的路线,如何找到他们的一条交边呢?
答案是树上差分(出题人:黑科技啊)
其对于一条路径(差分数组)的处理为:将它起点的边+1,终点的边+1,lca上面的边-2。实质为起点和终点分别向其lca差分。
但我们很难直接确定一条边,这里将边权向下映射到点权即可,根节点则没有对应值。(出题人:树上问题的常用技巧)
每次求一遍树上前缀和(具体过程见下面补充,卡常真有趣2333),若存在一条边,其经过次数等于所有大于mid的路线的个数,且删除这条边后是所有路线的长度小于等于mid,后者的处理为这条边的长度大于等于所有路径与mid的最大差值。那么我们就找到了一个合法的答案。
注意二分写法,因题而异注意在保持固有风格的基础上适当做出符合题意的调整。
补充:
1.对于求树上前缀和,因为本题为树结构,这里只需要统计对答案有影响的点的值即可,即只通过长度为1的父子关系更新次数而不用一直向上更新。
2.再者需要对dfs序从后往前倒序求前缀和,因为这样是没有后效性的,因为我们dfs时先访问父亲,再访问儿子,父亲的dfs序必然小于它儿子的dfs序,如果从后往前,必然先更新儿子,再更新父亲,导致前缀和偏小。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define RI register int
using namespace std;
typedef long long ll;
int n,m,ru,rv,rw,tot,a,b,ls,rs;
int dis[1000010],seq[1000010],dif[1000010],val[1000010],first[2000010],nxt[2000010],top[1000010],siz[1000010],son[1000010],f[1000010],deep[1000010];
bool flag;
struct edge//树边
{
int u,v,w;
}l[3000010];
struct inte//路径
{
int f,t,lca,dis;
}p[3000010];
int read()
{
char ch=getchar();
int ret=0;
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9')
{
ret=ret*10+(ch-'0');
ch=getchar();
}
return ret;
}
void build(int f,int t,int c)
{
l[++tot]=(edge){f,t,c};
nxt[tot]=first[f];
first[f]=tot;
}
void dfs_1(int k,int fa,int d,int s)
{
deep[k]=d;
dis[k]=s;//权值前缀和
f[k]=fa;
siz[k]=1;
seq[++tot]=k;//储存dfs序,用于求前缀和,对递归的常数优化,也可以topsort
for(int i=first[k];i!=-1;i=nxt[i])
{
int x=l[i].v;
if(x==fa)
continue;
val[x]=l[i].w;//边权赋为点权,切记要放在continue下面
dfs_1(x,k,d+1,s+l[i].w);
siz[k]+=siz[x];
if(!son[k]||siz[x]>siz[son[k]])
son[k]=x;
}
}
void dfs_2(int k,int num)
{
top[k]=num;
if(!son[k])
return;
dfs_2(son[k],num);
for(int i=first[k];i!=-1;i=nxt[i])
{
int x=l[i].v;
if(x!=son[k]&&x!=f[k])
dfs_2(x,x);
}
}
int find_lca(int x,int y)//树剖lca
{
int f1=top[x],f2=top[y];
while(f1!=f2)
{
if(deep[f1]<deep[f2])
{
f1=f1^f2;//位运算交换
f2=f1^f2;
f1=f1^f2;
x=x^y;
y=x^y;
x=x^y;
}
x=f[f1];
f1=top[x];
}
return deep[x]<=deep[y]?x:y;
}
int check(int k)
{
memset(dif,0,sizeof(dif));
int cnt=0,limit=-1e9+7;
for(int i=1;i<=m;i++)
{
if(p[i].dis>k)//找出所有长度大于x的路径
{
limit=max(limit,p[i].dis-k);//最大差值
cnt++;
dif[p[i].f]++;//树上差分
dif[p[i].t]++;//实质为两点分别向lca差分
dif[p[i].lca]-=2;
}
}
if(!cnt)
return 1;
for(int i=tot;i>=1;i--)//tot和n皆可,但必须倒序----在将一个父节点的次数用其子节点更新完成后,才会将此父节点的前缀和加入她的父节点中
dif[f[seq[i]]]+=dif[seq[i]];//统计次数
for(int i=2;i<=n;i++)//将边权映射为点权,向下映射,第一个点无对应边
if(dif[i]==cnt&&val[i]>=limit)
return 1;
return 0;
}
int main()
{
memset(first,-1,sizeof(first));
n=read();
m=read();
for(int i=1;i<=n-1;i++)
{
ru=read();
rv=read();
rw=read();
build(ru,rv,rw);
build(rv,ru,rw);
rs+=rw;
}
tot=0;
dfs_1(1,0,1,0);
dfs_2(1,1);
for(int i=1;i<=m;i++)
{
a=read();
b=read();
int tmp=find_lca(a,b);
p[i].f=a;
p[i].t=b;
p[i].dis=dis[a]+dis[b]-2*dis[tmp];//求出路径长度
p[i].lca=tmp;
}
ls=-1;
while(ls+1<rs)//二分删掉一条边后的最大路线的长度
{
int mid=(ls+rs)>>1;
if(check(mid))
rs=mid;
else ls=mid;
}
printf("%d\n",rs);
return 0;
}
本题只要分析出题设要求和树结构的性质,在思路方面并无太大障碍,但涉及的知识点较多,彼此之间联系紧密,考察在考场上对算法掌握的熟练程度和敏锐的判断力。