2019浙江省赛 A-Vertices in the Pocket

题意:无向图  修改加查询

          修改:在无向图中加边( 保证简单图:无重边和自环 )

          查询:加x条边(保证加完还是简单图),最多和最少的连通分量

思路:

         首先思路很好想,连通块尽量少就是连一条边减少一个连通分量。连通块尽量多就是在原有的连通分量中进行加边。

        加满了就需要在两个连通块之间进行连边,我们当然希望在两个大的连通块之间进行连边。所以涉及到一个按连通块大小进

        行排序的问题。易知前n个连通块所能容纳的边数与 “和”和“ 平方和 ” 有关。这是一个前缀和有关的东西,确定需要连通前  几  个连通块的方法大致可以想到是二分,于是不难想在用权值线段树上进行二分。(因为需要二分所以线段树要优于树状数组)。

       维护的东西都很简单,直接上代码。

       我又被 int 坑了,以后还是用 longlong 吧,T了再说。

#include <iostream>
#define lc l,mid,x << 1LL
#define rc mid+1,r,x << 1LL | 1LL
using namespace std;
typedef long long LL;
const int maxn = 100000 + 100;
LL num1[5*maxn],num2[5*maxn],num0[5*maxn];
void pushup( LL x ){
    num1[x]  = num1[x<<1] + num1[x<<1|1];
    num2[x] = num2[x<<1] + num2[x<<1|1];
    num0[x] = num0[x<<1] + num0[x<<1|1];
}
LL n;
void build( LL l,LL r,LL x){
    if( l == r ){
        if( l == 1 ){
            num1[x] = n;
            num2[x] = n;
            num0[x] = n;
        }
        else{
            num1[x] = 0;
            num2[x] =0;
            num0[x] = 0;
        }
        return;
    }
    LL mid = l + r >> 1LL;
    build( lc );
    build( rc );
    pushup( x );
}
void update( LL L,LL v,LL l,LL r,LL x ){
    if( l == r ){
        num1[x] += v*l;
        num2[x] += v * l * l;
        num0[x] += v;
        return;
    }
    LL mid = l + r >> 1LL;
    if( L <= mid )update( L,v,lc );
    else update( L,v,rc );
    pushup(x );
}
LL ask( LL sum,LL le,LL l,LL r,LL x ){
    if( l == r ){
        LL ll = -1,rr = num0[x];
        while( ll != rr-1 ){
            LL mid = ll + rr >> 1LL;
            if( (mid * l * mid * l - mid * l*l) / 2 + mid * l * sum >= le ){
                rr = mid;
            }else{
                ll = mid;
            }
        }
        return rr  ;
    }
    LL sum0 = num0[x<<1LL |1LL];
    LL sum1 = num1[x<<1LL |1LL];
    LL sum2 = num2[x<<1LL |1LL];
    LL re = (sum1 * sum1 - sum2) / 2 + sum1 * sum;
    LL mid=  l + r >> 1LL;
    if( re >= le ){
        return ask( sum,le,rc );
    }else{
        return sum0 + ask( sum +sum1,le-re,lc );
    }

}
LL cnt[maxn],fa[maxn];
void init(LL n){
    for( LL i = 0;i <= n;i++ ){
        fa[i] = i;
        cnt[i] = 1;
    }
}
LL get( LL x ){
    if( x == fa[x] ) return x;
    return fa[x] = get( fa[x] );
}
int main()
{
    LL T,q,f,x,y,remain;
    LL contain;
    scanf("%lld",&T);
    while( T-- ){
        scanf("%lld%lld",&n,&q);
        remain = n;
        init(n);
        build( 1,n,1 );
        contain = 0;
        for( LL i = 1;i <= q;i++ ){
            scanf("%lld",&f);
            if( f == 1 ){
                scanf("%lld%lld",&x,&y);
                LL fx = get(x);
                LL fy = get(y);
                if( fx == fy ){
                    contain--;
                }else{
                    fa[fx] = fy;
                    remain--;
                    contain += (LL)cnt[fx] * cnt[fy] - 1;
                    update(cnt[fx],-1,1,n,1 );
                    update( cnt[fy],-1,1,n,1 );
                    cnt[fy] += cnt[fx];
                    cnt[fx] = 0;
                    update( cnt[fy],1,1,n,1 );
                }
            }else{
                scanf("%lld",&x);
                LL mn = max(1LL,remain-x);
                LL mm;
                if( x <= contain ){
                    mm = remain;
                }else{
                    mm = remain -  ask(0, x - contain,1,n,1 ) + 1;
                }
                printf("%lld %lld\n",mn,mm);
            }
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值