2 条题解

  • 0
    @ 2018-11-18 23:11:49

    二分答案
    题意为:给你一棵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
    @ 2018-11-18 16:53:35

    先贴上我的参考解题报告,晚点再出解题报告的解题报告。
    http://www.cnblogs.com/skylee03/p/9939772.html

  • 1

3# 赛道修建( 长郡数据)

信息

难度
8
分类
(无)
标签
递交数
24
已通过
5
通过率
21%
上传者