【NOIP2015模拟11.4】电话线铺设 jzoj 4313 最小生成树+树链剖分

13 篇文章 0 订阅
7 篇文章 0 订阅

题目

这里写图片描述

分析

要把n个点用n-1条边连接起来,还要使总边权和最小,那么很明显是求最小生成树,用克鲁斯卡尔算法可解。

但是这题有一个限制条件:
一定要用n-2条王牌电缆+1条李牌电缆组成最小生成树。

那我们就先用n-1条王牌电缆建立最小生成树。
然后枚举所有李牌的边,如果一条李牌边是从x到y费用z,因为要使结果是一颗树,所以要把新生成的环上边权最大的边删去(即在x到y的路径上删去一条最大的王牌的边)
这个边显然可以用倍增求lca的方法找出.

但是

我忘了倍增怎么写……

所以我用了一个树链剖分来求(大材小用)……

ps:有一种情况是王牌的边建完最小生成树之后只有n-2条边,所以需要进行特判。

pss:我tm的改了两个钟就是因为一个初始化没打和一个树链剖分的sb细节错误???

psss: 280+行的程序也是够了,有毒.

code

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<string>
#include<algorithm>
#define maxn 300005  
#define maxm 600005 
#define INF 0x7fffffff;

using namespace std;

struct gg{
    int x,y,w;
    int next;
    int wei;
}edge[maxm];
int ls[maxn];
int edge_m;

gg edge1[maxm];
int edge_m1;

bool cmp(gg a,gg b)
{
    return a.w<b.w;
}

struct ar
{
    int max,wei;
};

struct arr{
    int x,y;
    ar max;
}f[maxm*10];

int a[maxn];
int n,W,L;

void swap1(int &x,int &y)
{
    int z;
    z=x; x=y; y=z;
    return;
}

void add1(int x,int y,int w,int wei)
{
    edge1[++edge_m1]=(gg){x,y,w,0,wei};
}

void add(int x,int y,int w,int wei)
{
    edge[++edge_m]=(gg){x,y,w,ls[x],wei},ls[x]=edge_m;
    edge[++edge_m]=(gg){y,x,w,ls[y],wei},ls[y]=edge_m;
}

int insert(int r,int x,int y,int add,int wei)//线段树修改操作。
{
    if (f[r].max.max<add) f[r].max.max=add,f[r].max.wei=wei;
    if ((f[r].x==x)&&(f[r].y==y)) 
    {
        return 0;
    }
    int mid=(f[r].x+f[r].y)/2;
    if (y<=mid) insert(r*2,x,y,add,wei);
        else if (x>mid) insert(r*2+1,x,y,add,wei);
            else insert(r*2,x,mid,add,wei),insert(r*2+1,mid+1,y,add,wei);
}

int maketree(int r,int x,int y)//线段树建树
{
    f[r].x=x; f[r].y=y; 
    f[r].max.max=0;
    if (x==y) return 0;
    int mid=(x+y)/2;
    maketree(r*2,x,mid);
    maketree(r*2+1,mid+1,y);
}

int siz[maxn],dep[maxn],top[maxn],fa[maxn],son[maxn],w[maxn],ed[maxn];

void dfs1(int x,int r)//树链dfs1 
{
    fa[x]=r;
    siz[x]=1;
    dep[x]=dep[r]+1;
    int mx=0;
    for (int i=ls[x];i;i=edge[i].next)
    {
        if (edge[i].y==r) continue;
        dfs1(edge[i].y,x);
        ed[edge[i].y]=i;
        siz[x]+=siz[edge[i].y];
        if (mx<siz[edge[i].y])
        {
            mx=siz[edge[i].y];
            son[x]=edge[i].y;
        }
    }
    return;
}

int num=0;

void dfs2(int x,int st)//树链dfs2 
{
    num++;
    w[x]=num; top[x]=st;
    if (son[x]!=0)  dfs2(son[x],st);
    for (int i=ls[x];i;i=edge[i].next)
    {
        if ((edge[i].y!=fa[x])&&(edge[i].y!=son[x]))
            dfs2(edge[i].y,edge[i].y);
    }
    return;
}

ar findmax(int r,int x,int y)//线段树 
{
    if ((f[r].x==x)&&(f[r].y==y)) return f[r].max;
    int mid=(f[r].x+f[r].y)/2;
    if (y<=mid) return findmax(r*2,x,y);
        else if (x>mid) return findmax(r*2+1,x,y);
    ar k1=findmax(r*2,x,mid),k2=findmax(r*2+1,mid+1,y);
    if (k1.max>=k2.max)
        return k1;
    else
        return k2;
}

ar solvemax(int x,int y)//树链 
{
    ar mx=(ar){0,0};
    int f1=top[x];
    int f2=top[y];
    while (f1!=f2)
    {
        if (dep[f1]<dep[f2]) 
        {swap1(x,y); swap1(f1,f2);};
        ar k1=findmax(1,w[f1],w[x]);
        if (k1.max>mx.max) mx=k1;
        x=fa[f1]; f1=top[x];
    }
    if (x==y) return mx;
    if (dep[x]>dep[y]) swap1(x,y);
    x=son[x];
    ar k1=findmax(1,w[x],w[y]);
    if (k1.max>mx.max) mx=k1;
    return mx;
}

int father[maxn];

int found(int x)
{
    if (father[x]==x) return x;
    else{
        father[x]=found(father[x]);
        return father[x];
    }
}

void merge(int x,int y)
{
    int x1,y1;
    x1=found(x);
    y1=found(y);
    father[x1]=y1;
}

int ans=0,mxx;

void tree()
{
    sort(edge1+1,edge1+1+W,cmp);
    for (int i=1;i<=n;i++) father[i]=i;
    int i=1; num=0;
    while ((num<n-1)&&(i<=W))
    {
        if (found(edge1[i].x)!=found(edge1[i].y))
        {

            ans+=edge1[i].w;
            //printf("%d %d\n",ans,i);
            merge(edge1[i].x,edge1[i].y);
            add(edge1[i].x,edge1[i].y,edge1[i].w,edge1[i].wei);
            num++;
        }
        i++;
    }
    //printf("\n");
    mxx=ans;
}

void init()
{
    scanf("%d%d%d\n",&n,&W,&L);
    for (int i=1;i<=W;i++)
    {
        int x,y,w;
        scanf("%d%d%d\n",&x,&y,&w);
        add1(x,y,w,i);
    }
}

long long mi=INF;

int sp()
{
    int num=found(1),flag=0,u,v;
    for (int i=2;i<=n;i++)
    {
        found(i);
        if (father[i]!=num) 
        {
            u=num;
            v=father[i];
            flag=1;
            break;
        }
    }
    for (int i=1;i<=n;i++)
    {
        found(i);
        //printf("%d\n",father[i]);
    }
    if (flag)
    {
        int anss=0;
        for (int i=1;i<=L;i++)
        {
            int x,y,w;
            scanf("%d%d%d",&x,&y,&w);
            if (((father[x]==v)&&(father[y]==u))||((father[y]==v)&&(father[x]==u)))
                if (w<mi) mi=w,anss=i;
        }
        printf("%d\n",ans+mi);
        for (int i=1;i<=edge_m;i+=2)
        {
            printf("%d\n",edge[i].wei);
        }   
        printf("%d",anss);
    }
    return flag;
}

int main()
{
    freopen("telephone.in","r",stdin);
    freopen("telephone.out","w",stdout);
    init();
    tree();
    if (sp()) 
    {
        fclose(stdin);
        fclose(stdout);
        return 0;
    }
    maketree(1,1,maxm*2);
    dfs1(1,0);
    dfs2(1,1);
    for (int i=1;i<=n;i++)
    {
        insert(1,w[i],w[i],edge[ed[i]].w,edge[ed[i]].wei);
    }
    int wei=0,wei2=0;
    int ans=INF;
    for (int i=1;i<=L;i++)
    {       
        int x,y,w;
        scanf("%d%d%d",&x,&y,&w); 
        ar k=solvemax(x,y);
        if (ans>mxx-k.max+w) ans=mxx-k.max+w,wei=k.wei,wei2=i;
    }
    printf("%d\n",ans);
    for (int i=1;i<=edge_m;i+=2)
    {
        if (edge[i].wei!=wei) printf("%d\n",edge[i].wei);
    }
    printf("%d",wei2);
    fclose(stdin);
    fclose(stdout);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值