2 条题解
-
0Guest LV 0
-
0
二分答案
题意为:给你一棵n(n≤5×10^4)个结点的树,从中找出m个没有公共边的路径,使得第m长的路径最长。问第m长的路径最长可以是多少。
由于你根本不晓得第m长的路径究竟是多少,所以只能有技巧地爆搜。所谓的技巧就是二分答案!理由如下:
1、极端情况是m=1,且这是一棵一叉树,则最长路径必然是所有的边和,假设为R。
2、设L=0,假设第m长的路径最长为lim,则lim可取的区间值为[L,R]。
3、通过二分答案不断试探lim,找到最终答案!(时间复杂度为lomx(n))
4、用calc(lim)来统计长度达到lim的路径数量。若calc(lim)>=m,说明设定的lim小了,答案在[lim+1,R]区间中;反之,答案在区间[L,lim-1]中。二分答案框架:
l=0;r=所有边和(输入时可累加);
while(l<=r) {
const int lim=(l+r)>>1;
if(calc(lim)>=m) {//函数calc(lim)用于统计路径>=lim的数量。
l=lim+1;
} else {
r=lim-1;
}
}
接下去的终点是统计满足条件的路径数量了。
以x节点为例,有两种情况:
情况1:以x为起点(即以x为根)有多条>=lim的路径,设路径数为num[x],y为x的儿子节点。则num[x]由以下两种情况组成:
(1)若以儿子y为起点的路径>=lim,则以父节点为起点的路径也必然>lim 。因此: num[x]=num[x]+num[y];
(2)设儿子节点y起算的区间路径长度之一为mx[y,i](以y为起点可遍历到第i条路径)。若mx[y,i]<lim,但dis[x,y]+mx[y,i]>=lim, 则num[x]++;
情况2: 除去情况1,另一种情况是x为路径的中间点所得到的路径长度>lim。即mx[x,i]+mx[x,j]>=lim,这也是一种方案数。为了保证第m长最长,需要做一个优化处理:把所有的mx[x,i]排序,最k大值和最k小值搭配,若和>lim,方案数+1;否则,由于每条边只能走一遍,保留mx[y,i]中的最长路径优化一:
几叉树不能确定,以x为起点的路径数不能确定,又需对路径长度进行排序,可采用<set>容器中的multiset进行存储,利用迭代器中的.bemxin()和.end()进行元素存取。关于set容器的使用请详见以下链接:
https://www.cnblomxs.com/ChinaHook/p/6985444.html
详细代码如下:#include<algorithm>
#include<iostream>
#include<cstdio>
#include<set> //集合头文件,包含 set 和multiset
#define MN 50000
using namespace std;
inline int read() //快读
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n,m,head[MN+5],cnt=0,num[MN+5],mx[MN+5]; //num[i]表示以i为根的满足题目的路径数, mx[i]表示以i的起点的路径长度
struct edge{int to,next,w;}e[MN*2+5]; //结构体
inline void ins(int f,int t,int w) //双向建边
{
e[++cnt]=(edge){t,head[f],w};head[f]=cnt;
e[++cnt]=(edge){f,head[t],w};head[t]=cnt;
}
multiset<int> st; //定义允许元素重复的集合
void Solve(int x,int fa,int mid)
{
num[x]=mx[x]=0;
for(int i=head[x];i;i=e[i].next)
if(e[i].to!=fa) Solve(e[i].to,x,mid),num[x]+=num[e[i].to];//计算父节点
for(int i=head[x];i;i=e[i].next)
if(e[i].to!=fa)
{
mx[e[i].to]+=e[i].w;
if(mx[e[i].to]>=mid) ++num[x];//计算子节点
else st.insert(mx[e[i].to]); //将区间路径插入集合中,默认降序
}
while(!st.empty())
{
int v=*st.begin();st.erase(st.begin()); //取出区间最长路径 .begin()表示取集合中第一个元素;.erase()表示删除集合中某个位置的元素
multiset<int>::iterator it=st.lower_bound(mid-v); //定义一个 指向 multiset<int>类型的 迭代器 it, iterator:迭代器,相当于指针;
//.lower_bound(mid-v)取集合中>=mid-v的第一个位置
if(it==st.end()) mx[x]=max(mx[x],v); //it==st.end(),判断it是否在st迭代器中,即判断 mid-v的路径是否存在
else ++num[x],st.erase(it);
}int main()
{
n=read();m=read();
int l=1,r=0;
for(int i=1;i<n;++i)
{
int x=read(),y=read(),w=read();
ins(x,y,w); r+=w;
}
int mid,res=1;
while(l<=r)
{
mid=(l+r)>>1;
Solve(1,0,mid);
if(num[1]>=m) res=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",res);
return 0;
} -
0
先贴上我的参考解题报告,晚点再出解题报告的解题报告。
http://www.cnblogs.com/skylee03/p/9939772.html
- 1
信息
- 难度
- 8
- 分类
- (无)
- 标签
- 递交数
- 24
- 已通过
- 5
- 通过率
- 21%
- 上传者