[2017纪中10-28]图 最小生成树+LCT

题面
把图分成正图(k+x)和负图(k-x),且称正树为正图中的最小生成树,负树为负图中的最小生成树。
有个很明显的结论是,无论 x 的值是多少,最后的最小生成树的边一定是正图中的最小生成树上的边或负图的最小生成树上的边(因为正图和负图都是联通图)。
当 x 趋近于无穷小的时候,正树就是图的最小生成树,当 x 逐渐增大,负树中的边会逐渐取代正树中的边(但不一定按照从小到大顺序取代),例如样例。
但是这样并不影响我们从小到大加入负树中的边,因为上面的情况只会出现在两边产生的环没有边相交的情况,而如果有相交的部分,那么 k 比较小的会先做出决策。
然后就得到如下算法:
先分别做最小生成树算法得到正树和负树,以正树作为初始版本的最小生成树,然后将负树上的边从小到大加进生成树中,对于一条负边

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int maxn=100010;
const int maxm=200010;
const int infk=1000000010;
int n,a,b,Q,t,fa[maxn];
ll sum[maxm];
struct edge
{
    int t,k,f;
    bool h;
    edge *next,*rev;
}*con[maxn];
struct node
{
    int u,v,k,f;
    edge *pt;
}ea[maxm],eb[maxm],e[maxm];

struct node2
{
    ll s;
    int lx,tp;
}d[maxm];
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;
}
void ins(int x,int y,int k,int f,bool h)
{
    edge *p=new edge;
    p->t=y;
    p->k=k;
    p->f=f;
    p->h=h;
    p->next=con[x];
    con[x]=p;
}
bool cmp(node a,node b)
{
    return a.k+a.f*t<b.k+b.f*t;
}
bool cmp2(node2 a,node2 b)
{
    return a.lx<b.lx;
}
int getfa(int v)
{
    if(fa[v]==v) return v;
    fa[v]=getfa(fa[v]);
    return fa[v];
}
void MST0(node *ex,int &sz)
{
    int tot=0,chs=0;
    sort(ex+1,ex+sz+1,cmp);
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=sz;i++)
        if(getfa(ex[i].u)!=getfa(ex[i].v))
        {
            fa[getfa(ex[i].u)]=getfa(ex[i].v);
            ex[++tot]=ex[i];
            chs++;
            if(chs>=n-1) break;
        }
    sz=tot; 
}
ll MST(int &ttp)
{
    sort(e+1,e+a+b+1,cmp);
    int chs=0;ttp=0;ll ans=0;
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=a+b;i++)
        if(getfa(e[i].u)!=getfa(e[i].v))
        {
            fa[getfa(e[i].u)]=getfa(e[i].v);
            ans+=e[i].k+e[i].f*t;
            if(e[i].f==1) ttp++;
            chs++;
            if(chs>=n-1) break;
        }
    return ans; 
}
bool find(int v,int fa,int goal,edge *&mxe)
{
    bool re=0;
    for(edge *p=con[v];p;p=p->next)
        if(p->t!=fa&&p->h)
        {
            if(p->t==goal) re=1;
            else {re=re||find(p->t,v,goal,mxe);}
            if(re)
            {
                if((mxe==NULL||p->k>mxe->k)&&p->f==1) mxe=p;
                return 1;
            }
        }
    return 0;   
}
int main()
{
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    n=read();a=read();b=read();Q=read();
    for(int i=1;i<=a;i++)
        ea[i].u=read(),ea[i].v=read(),ea[i].k=read(),ea[i].f=1;
    for(int i=1;i<=b;i++)
        eb[i].u=read(),eb[i].v=read(),eb[i].k=read(),eb[i].f=-1;
    if(n>1000)  
    {
        for(int i=1;i<=a;i++)
            e[i]=ea[i];
        t=b=0;  
        ll base=MST(b);
        while(Q--)
        {
            t=read();
            printf("%lld\n",base+(ll)t*(n-1));
        }
    }
    else
    {
    t=0;
    MST0(ea,a);
    MST0(eb,b); 
    for(int i=1;i<=a;i++)
    {
        e[i]=ea[i];
        ins(ea[i].u,ea[i].v,ea[i].k,ea[i].f,1);
        ins(ea[i].v,ea[i].u,ea[i].k,ea[i].f,1);
        con[ea[i].u]->rev=con[ea[i].v];
        con[ea[i].v]->rev=con[ea[i].u];
        ea[i].pt=con[ea[i].u];
    }
    for(int i=1;i<=b;i++)
    {
        e[i+a]=eb[i];   
        ins(eb[i].u,eb[i].v,eb[i].k,eb[i].f,0);
        ins(eb[i].v,eb[i].u,eb[i].k,eb[i].f,0);
        con[eb[i].u]->rev=con[eb[i].v];
        con[eb[i].v]->rev=con[eb[i].u];
        eb[i].pt=con[eb[i].u];
    }
    t=-1e9;d[0].s=MST(d[0].tp);d[0].lx=t;
    for(int i=1;i<=b;i++)
    {
        edge *mxe=NULL;
        find(eb[i].u,0,eb[i].v,mxe);
        mxe->h=mxe->rev->h=0;
        eb[i].pt->h=eb[i].pt->rev->h=1;
        t=ceil((double)(eb[i].k-mxe->k)/2+0.01);
        d[i].s=MST(d[i].tp);d[i].lx=t;
        d[i].tp-=(n-1-d[i].tp);
    }
    sort(d,d+b+1,cmp2);
    while(Q--)
    {
        t=read();
        int l=0,r=b+1;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if(d[mid].lx<=t) l=mid+1;
            else r=mid;
        }
        l--;
        printf("%lld\n",d[l].s+(ll)d[l].tp*(t-d[l].lx));
    }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值