[树上倍增][最小生成树]JZOJ P4313——电话线铺设

Description
这里写图片描述
Input
这里写图片描述

Output

这里写图片描述

Sample Input

6 9 4
6 3 4
2 5 6
5 4 6
1 3 5
3 5 9
5 6 8
4 1 5
4 6 4
6 2 7
2 5 3
1 5 4
4 5 4
3 2 5

Sample Output

22
1
8
4
3
1

Data Constraint
这里写图片描述

题解

我们先对全部的“王牌电缆”建一颗最小生成树
那么对于要加如来的一条“李牌电缆”,一定会使最小生成树形成一个环(除数据12、13点外)
如果要使最后得出来的解最小,那么就是要删掉在加入该条“李牌电缆”后形成环上最大的边
那么我们如果找到这个环?
易得,如果连接“李牌电缆”两点的最近公共祖先,和这两个点围成的就是要求得的环
那么,我们可以用倍增求Lca,可支持O(log n),询问两点之间的费用最大的边权
(Tips:
    如果存在只用“王牌电缆”无法联通整个小区的数据
    直接枚举加入最小生成数里
    取最小值输出
)

拓展 :树上倍增求LCA

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=800007;
const int inf=0x7fffffff;
int l,t,n,m,w,e,num,x,y,z,mn,mnn,mnm;
int father[maxn],ans,first[maxn],next[maxn],last[maxn],total,zhi[maxn];
int x1[maxn],y1[maxn],z1[maxn],f[maxn][20],k[maxn][20],aa,g[maxn][20],xu[maxn];
int ans1[maxn],bb,deep[maxn];
struct edge{int v,d,l,r;}a[maxn];
bool cmp(edge x,edge y){ return (x.v<y.v)||(x.v==y.v&&x.l<y.l)||(x.v==y.v&&x.l==y.l&&x.r<y.r); }
int getfather(int x)
{
    if (!father[x]) return x;
    father[x]=getfather(father[x]); 
    return father[x];
}
void insert(int x,int y,int z,int d)
{
    last[++total]=y;
    next[total]=first[x];
    first[x]=total;
    zhi[total]=z; 
    xu[total]=d;
    last[++total]=x;
    next[total]=first[y];
    first[y]=total;
    zhi[total]=z;
    xu[total]=d;
}
void dfs(int x,int y)
{
    f[x][0]=y; deep[x]=deep[y]+1;
    for (int i=first[x];i;i=next[i])
        if (last[i]!=y)
        {
            dfs(last[i],x);
            k[last[i]][0]=zhi[i];
            g[last[i]][0]=xu[i];
        }
}
int lca(int x,int y)
{
    int w=0;
    if (deep[x]<deep[y]) swap(x,y);
    for (int i=19;i>=0;i--)
        if (deep[f[x][i]]>deep[y])
        {
            bb=(k[x][i]>w?g[x][i]:bb);
            w=max(w,k[x][i]);
            x=f[x][i];
        }
    if (deep[x]!=deep[y])
    {
        bb=(k[x][0]>w?g[x][0]:bb);
        w=max(w,k[x][0]);
        x=f[x][0];
    }
    for (int i=19;i>=0;i--) 
        if(f[x][i]!=f[y][i]) 
        {
            bb=(k[x][i]>w?g[x][i]:bb);
            w=max(w,k[x][i]);
            bb=(k[y][i]>w?g[y][i]:bb);
            w=max(w,k[y][i]);
            x=f[x][i];
            y=f[y][i];
        }
    if (x!=y) 
    {
        bb=(k[x][0]>w?g[x][0]:bb);
        w=max(w,k[x][0]);
        bb=(k[y][0]>w?g[y][0]:bb);
        w=max(w,k[y][0]);
    }
    aa=w;
    if (x!=y) return f[x][0]; else return x;
}
int main()
{
    freopen("telephone.in","r",stdin);
    freopen("telephone.out","w",stdout);
    scanf("%d%d%d",&n,&w,&l);
    for (int i=1;i<=w;i++)
    {
        scanf("%d%d%d",&x,&y,&z);
        if (x>y) swap(x,y);
        a[++num].l=x; a[num].r=y; a[num].v=z; a[num].d=i;
    }
    sort(a+1,a+num+1,cmp);
    for (int i=1;i<=l;i++) scanf("%d%d%d",&x1[i],&y1[i],&z1[i]);
    mn=inf;
    for (int i=1;i<=num;i++)
    {
        x=getfather(a[i].l); y=getfather(a[i].r);
        if (x!=y)
        {
            father[y]=x; 
            m++;
            ans+=a[i].v;
            ans1[m]=a[i].d;
            insert(a[i].l,a[i].r,a[i].v,a[i].d);
        }
        if (m==n-1) break;
    }
    if (m==n-2)
    {
        for (int i=1;i<=l;i++)
        {
            x=getfather(x1[i]); y=getfather(y1[i]);
            if (x!=y&&z1[i]<mn)
            {
                mn=z1[i];
                mnn=i;
            }
        }
        printf("%d\n",ans+mn);
        for (int i=1;i<=m;i++) printf("%d\n",ans1[i]);
        printf("%d\n",mnn);
        return 0;
    }
    dfs(1,0);
    for (int j=1;j<=19;j++)
        for (int i=1;i<=n;i++)
        {
            f[i][j]=f[f[i][j-1]][j-1];
            if (f[i][j]) g[i][j]=(k[i][j-1]>k[f[i][j-1]][j-1]?g[i][j-1]:g[f[i][j-1]][j-1]);
            if (f[i][j]) k[i][j]=max(k[i][j-1],k[f[i][j-1]][j-1]);
        }
    for (int i=1;i<=l;i++)
    {
        int fa=lca(x1[i],y1[i]);
        if (z1[i]-aa<mn)
        {
            mn=z1[i]-aa;
            mnn=i;
            mnm=bb;
        }
    }
    printf("%d\n",ans+mn);
    for (int i=1;i<=n-1;i++)
    {
        if (ans1[i]==mnm) continue;
        printf("%d\n",ans1[i]);
    }
    printf("%d\n",mnn);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值