题目描述
C 城将要举办一系列的赛车比赛.在比赛前,需要在城内修建m条赛道.
C 城一共有n个路口,这些路口编号为1,2,…,n,有 n-1 条适合于修建赛道的双向通行的道路,每条道路连接着两个路口.其中,第i条道路连接的两个路口编号为
a
i
{a_i}
ai和
b
i
{b_i}
bi,该道路的长度为
l
i
{l_i}
li.借助这n-1条道路,从任何一个路口出发都能到达其他所有的路口。
一条赛道是一组互不相同的道路
e
1
,
e
2
,
…
,
e
k
{e_1,e_2,…,e_k}
e1,e2,…,ek,满足可以从某个路口出发,依次经过 道路
e
1
,
e
2
,
…
,
e
k
{e_1,e_2,…,e_k}
e1,e2,…,ek.
(每条道路经过一次,不允许调头)到达另一个路口.一条赛道的长度等于经过的各道路的长度之和.为保证安全,要求每条道路至多被一条赛道经过。
目前赛道修建的方案尚未确定.你的任务是设计一种赛道修建的方案,使得修建的m条赛道中长度最小的赛道长度最大.(即m条赛道中最短赛道的长度尽可能大)
输入格式
输入文件第一行包含两个由空格分隔的正整数n,m,分别表示路口数及需要修建的赛道数。
接下来n-1行,第i行包含三个正整数 a i , b i , l i {a_i,b_i,l_i} ai,bi,li表示第i条适合于修建赛道的道 路连接的两个路口编号及道路长度。保证任意两个路口均可通过这n-1条道路相互到达。每行中相邻两数之间均由一个空格分隔。
输出格式
输出共一行,包含一个整数,表示长度最小的赛道长度的最大值.
说明/提示
【输入输出样例 1 说明】
所有路口及适合于修建赛道的道路如下图所示:
道路旁括号内的数字表示道路的编号,非括号内的数字表示道路长度. 需要修建1条赛道.可以修建经过第3,1,2,6条道路的赛道(从路口4到路口7), 则该赛道的长度为 9+10+5+7 = 31,为所有方案中的最大值.
【输入输出样例 2 说明】
所有路口及适合于修建赛道的道路如下图所示:
需要修建3条赛道。可以修建如下3条赛道:
经过第 1,6条道路的赛道(从路口1到7),长度为 6+9=15;
经过第5,2,3,8条道路的赛道(从路口6到9),长度为4+3+5+4=16;
经过第7,4条道路的赛道(从路口8到5),长度为7+10=17.长度最小的赛道长度为15,为所有方案中的最大值。
数据规模与约定
2 ≤ n ≤ 5 × 1 0 4 , 1 ≤ m ≤ n − 1 , 1 ≤ a i , b i ≤ n , 1 ≤ l i ≤ 1 0 4 {2 \le n \le 5\times 10^4, \ 1 \le m \le n − 1,\ 1 \le a_i,b_i \le n,\ 1 \le l_i \le 10^4} 2≤n≤5×104, 1≤m≤n−1, 1≤ai,bi≤n, 1≤li≤104.
解题思路
本题适合分点解决问题,即先拿部分暴力分,然后逃再思考正解.
这种方法可以较容易地帮助我们在考场上拿到较高得分.
1.
b
i
=
a
i
+
1
(
图
为
一
条
链
)
o
p
t
s
(
20
分
)
{1.b_i=a_i+1(图为一条链)opts(20分)}
1.bi=ai+1(图为一条链)opts(20分).
这种情况很简单,即将一个序列分为m个区间,求可以分到的最小区间和的最大值.(经典贪心问题,部分代码如下)时间复杂度:
O
(
n
l
o
g
n
)
{O(n~logn)}
O(n logn)
inline bool isok2(int kk)//核心部分
{
int num=0,zhi=0;
for(int i=1;i<n;i++)
{
if(zhi+b[i]>=kk)
{
zhi=0;
num++;
}
else zhi+=b[i];
if(num>=m)return true;
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].val);
b[a[i].x]=a[i].val;//便于写一些,无实质作用.
if(a[i].x!=a[i].y-1)tal2=0;
}
sort(a+1,a+n,cmp1);
l=1,r=1e9+7;
while(l<r)
{
mi=(l+r+1)>>1;
if(isok2(mi))l=mi,ans=mi;
else r=mi-1;
}
printf("%d\n",ans);
return 0;
}
2.
a
i
=
1
(
图
是
菊
花
图
)
o
p
t
s
(
20
分
)
{2.a_i=1(图是菊花图)opts(20分)}
2.ai=1(图是菊花图)opts(20分).
这种情况也很容易处理,可以把问题转化为在一堆数中取m次数,每次取取=一到两个数,使得取出的最小数和最大.(还是一个经典二分,方法就是先将
l
i
{l_i}
li从小到大排序,然后用指针在首尾各取一个再让指针逐渐向中间靠拢即可)时间复杂度:
O
(
n
l
o
g
n
)
{O(n~logn)}
O(n logn)
#include<bits/stdc++.h>
#define N 50005
using namespace std;
int n,m,tal1=1,tal2=1,ans,l,r,mi;
struct llj{
int x,y,val;}a[N];
int b[N];
inline bool cmp(const llj &a,const llj &b)
{
return a.val<b.val;
}
inline bool isok1(int kk)//菊花图核心代码
{
int num=0,zhi=0,ll=1,rr=n-1;
while(ll<=rr)
{
if(num>=m)return true;
zhi=a[rr].val,rr--;
if(zhi>=kk)
{
zhi=0;
num++;
continue;
}
else
{
while(zhi+a[ll].val<kk&&ll<=rr+1)ll++;
if(ll>rr)return false;
ll++;
zhi=0;
num++;
}
}
if(num>=m)return true;
return false;
}
inline bool isok2(int kk)
{
int num=0,zhi=0;
for(int i=1;i<n;i++)
{
if(zhi+b[i]>=kk)
{
zhi=0;
num++;
}
else zhi+=b[i];
if(num>=m)return true;
}
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].val);
b[a[i].x]=a[i].val;
if(a[i].x!=a[i-1].x&&i>1)tal1=0;//判是否为菊花图
if(a[i].x!=a[i].y-1)tal2=0;
}
if(tal1==1)
{
sort(a+1,a+n,cmp);
l=1,r=1e9+7;
while(l<r)
{
mi=(l+r+1)>>1;
if(isok1(mi))l=mi,ans=mi;
else r=mi-1;
}
}
else if(tal2==1)//处理
{
l=1,r=1e9+7;
while(l<r)
{
mi=(l+r+1)>>1;
if(isok2(mi))l=mi,ans=mi;
else r=mi-1;
}
}
printf("%d\n",ans);
return 0;
}
然后40分到手!接下来我们继续向高分迈进.
3.
m
=
1.
(
求
树
的
直
径
)
o
p
t
s
(
15
−
20
分
)
{3.m=1.(求树的直径)opts(15-20分)}
3.m=1.(求树的直径)opts(15−20分)
这就是板子(不会点这里),我就不过多的讲述了.(部分代码如下)
时间复杂度:
O
(
n
)
{O(n)}
O(n).
inline void lian(int x,int y,int z)
{
nxt[++tot]=fi[x];
fi[x]=tot;
to[tot]=y;
w[tot]=z;
}
inline int Dfs(int u,int fa)//核心部分树形DP
{
int sum1=0,sum2=0;
for(int i=fi[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa)continue;
sum2=max(sum2,Dfs(v,u)+w[i]);
if(sum1<sum2)swap(sum1,sum2);
}
ans=max(ans,sum1+sum2);
return sum1;
}
int main()
{
for(int i=1;i<n;i++)
{
lian(a[i].x,a[i].y,a[i].val);
lian(a[i].y,a[i].x,a[i].val);
rd[a[i].x]++,rd[a[i].y++];
}
Dfs(1,0);
printf("%d\n",ans);
return 0;
}
然后55分暴力就到手了.接下来我们想正解.
容易想到的是用树形DP,同时题面要求的是“最短长度最长”,就是明显的二分答案.
那么我们取min(a[i])为左边界,
Σ
i
=
1
n
a
[
i
]
{\Sigma_{i=1}^{n}a[i]}
Σi=1na[i],
a
[
i
]
{a[i]}
a[i]为右边界进行二分答案.
用两个数组
f
[
i
]
{f[i]}
f[i]和
g
[
i
]
{g[i]}
g[i]分别表示以i为根节点的子树中有多少条长度>=mid,去掉满足条件的路径后从ii往上连最长的长度.
在转移时将当前节点所有子节点的
g
[
i
]
{g[i]}
g[i]加上
w
[
i
]
{w[i]}
w[i]后加入一个multiset中,因为我们之后要先将长度满足>=mid的加到答案中,所以需要从大到小排序,而且不能去重,所以multiset是一个很好的选择.
加入后从后往前扫multiset的元素,如果>=mid加到答案(即f数组中)否则退出循环.
然后我们从前往后扫multiset中的元素,然后用lower_bound在multiset中找能够与它配对的元素,找不到的话就用这个元素来更新
g
[
i
]
{g[i]}
g[i],这样就可以得到最优解了.(AC代码如下)
#include<bits/stdc++.h>
#define N 150005
#define re register int
using namespace std;
int n,m,tal1=1,tal2=1,ans,l,r,mi,tot,sum;
struct llj{
int x,y,val;}a[N];
int b[N],fi[N],nxt[N],to[N],w[N],f[N],g[N];
multiset<int>s;
multiset<int>::iterator tt;
inline bool cmp1(const llj &a,const llj &b)
{
return a.val<b.val;
}
inline void lian(int x,int y,int z)
{
nxt[++tot]=fi[x];
fi[x]=tot;
to[tot]=y;
w[tot]=z;
}
inline bool isok1(int kk)
{
int num=0,zhi=0,ll=1,rr=n-1;
while(ll<=rr)
{
if(num>=m)return true;
zhi=a[rr].val,rr--;
if(zhi>=kk)
{
zhi=0;
num++;
continue;
}
else
{
while(zhi+a[ll].val<kk&&ll<=rr+1)ll++;
if(ll>rr)return false;
ll++;
zhi=0;
num++;
}
}
if(num>=m)return true;
return false;
}
inline bool isok2(int kk)
{
int num=0,zhi=0;
for(re i=1;i<n;i++)
{
if(zhi+b[i]>=kk)
{
zhi=0;
num++;
}
else zhi+=b[i];
if(num>=m)return true;
}
return false;
}
inline void Dfs(int u,int fa)//树形DP核心代码
{
for(re i=fi[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa)continue;
Dfs(v,u);
f[u]+=f[v];
}
for(re i=fi[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa)continue;
s.insert(g[v]+w[i]);
}
while(!s.empty())
{
int sum=*s.rbegin();
if(sum>=mi)
{
tt=s.find(sum);
f[u]++;
s.erase(tt);
}
else break;
}
while(!s.empty())
{
int sum=*s.begin();
s.erase(s.begin());
tt=s.lower_bound(mi-sum);
if(tt==s.end())g[u]=sum;
else
{
f[u]++;
s.erase(tt);
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(re i=1;i<n;i++)
{
scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].val);
b[a[i].x]=a[i].val;
if(a[i].x!=a[i-1].x&&i>1)tal1=0;
if(a[i].x!=a[i].y-1)tal2=0;
}
if(tal1==1)//保留特判是为了加速
{
sort(a+1,a+n,cmp1);
l=1,r=1e9+7;
while(l<r)
{
mi=(l+r+1)>>1;
if(isok1(mi))l=mi,ans=mi;
else r=mi-1;
}
}
else if(tal2==1)
{
l=1,r=1e9+7;
while(l<r)
{
mi=(l+r+1)>>1;
if(isok2(mi))l=mi,ans=mi;
else r=mi-1;
}
}
if(!tal1&&!tal2)
{
l=1e9+5,r=1;
for(re i=1;i<n;i++)
{
lian(a[i].x,a[i].y,a[i].val);
lian(a[i].y,a[i].x,a[i].val);
l=min(l,a[i].val);
r=r+a[i].val;
}
while(l<r)//核心二分
{
mi=(l+r+1)>>1;
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
Dfs(1,0);
if(f[1]>=m)
{
l=mi;
ans=max(ans,mi);
}
else r=mi-1;
}
}
printf("%d\n",ans);
return 0;
}
完结撒花!!!