zoukankan      html  css  js  c++  java
  • ZOJ 4100 浙江省第16届大学生程序设计竞赛 A题 Vertices in the Pocket 线段树+并查集

    正赛的时候完全没看这个题,事后winterzz告诉我他想出来的解法。

    首先题意是给出n个点,m次操作。

    操作有一种是连接两个点,另一种是求此时再为这个图连k条边,最少和最多能有几个联通块。

    最少的求法很简单,显然一条边可以减少一个联通块。

    最多的求法则稍微复杂:

    首先我们先将所有联通块填成完全图,这部分边是白给的。

    接下来最优的连接方式显然是将最大的和次大的联通块合并,如果还有边需要连就再将其把第三大的联通块合并...一直这样下去。

    这个东西我们显然可以二分,二分出将多少个联通块合并成一起能用完k个边。

    winterzz表示可以splay搞搞,只要支持插入一个点和求后缀和就可以了。

    但是仔细想想,其实我们用线段树也可以做到这个二分。

    我们做一个权值线段树来维护大小为k的块的个数,和它们的可容纳边总和,与它们的点个数总和,然后在这个线段树上二分。

    但是这样我们二分到子叶节点k就不知道要合并掉多少个大小为k的块了,所以我们在子叶节点再做一次二分即可,这样复杂度仍然只有一个log,因为每次查询我们只会到一个子叶节点。

    以下附上代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define mid (l+r)/2
    int i,i0,n,m,T,pre[100005],siz[100005];
    long long sum,num;
    int fin(int x){return (pre[x]==x)?x:pre[x]=fin(pre[x]);}
    void uni(int x,int y){if(fin(x)!=fin(y))pre[fin(y)]=fin(x);}
    struct node
    {
        long long siz,siz2,siz3;
    }tree[400005];
    node operator+(node a,node b){return {a.siz+b.siz,a.siz2+b.siz2,a.siz3+b.siz3};}
    void b_tree(int l,int r,int p)
    {
        if(l==r&&l==1)tree[p].siz=tree[p].siz3=n,tree[p].siz2=0;
        else tree[p].siz=tree[p].siz2=tree[p].siz3=0;
        if(l!=r)b_tree(l,mid,p*2),b_tree(mid+1,r,p*2+1);
    }
    void add_tree(int l,int r,int p,int a)
    {
        if(l==r)tree[p].siz+=l,tree[p].siz2+=l*(l-1)/2,tree[p].siz3++;
        else
        {
            if(a<=mid) add_tree(l,mid,p*2,a);
            else if(a>=mid+1)add_tree(mid+1,r,p*2+1,a);
            tree[p]=tree[p*2]+tree[p*2+1];
        }
    }
    void erase_tree(int l,int r,int p,int a)
    {
        if(l==r)tree[p].siz-=l,tree[p].siz2-=l*(l-1)/2,tree[p].siz3--;
        else
        {
            if(a<=mid) erase_tree(l,mid,p*2,a);
            else if(a>=mid+1)erase_tree(mid+1,r,p*2+1,a);
            tree[p]=tree[p*2]+tree[p*2+1];
        }
    }
    int q_tree(int l,int r,int p,long long k,long long v)
    {
        if(l==r)
        {
            int ll=1,rr=tree[p].siz3;
            while(ll<rr)
            {
                int mmid=(ll+rr)/2;
                if((v+mmid*l)*(v+mmid*l-1)/2>=l*(l-1)/2*mmid+k)rr=mmid;
                else ll=mmid+1;
            }
            return ll;
        }
        else
        {
            if((v+tree[p*2+1].siz)*(v+tree[p*2+1].siz-1)/2>=k+tree[p*2+1].siz2)return q_tree(mid+1,r,p*2+1,k,v);
            else return q_tree(l,mid,p*2,k+tree[p*2+1].siz2,v+tree[p*2+1].siz)+tree[p*2+1].siz3;
        }
    }
    int main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d %d",&n,&m);
            for(i=1;i<=n;i++)pre[i]=i,siz[i]=1;
            sum=0,num=n;
            b_tree(1,n,1);
            for(i=1;i<=m;i++)
            {
                int op;
                scanf("%d",&op);
                if(op==1)
                {
                    int x,y;
                    scanf("%d %d",&x,&y);
                    if(fin(x)!=fin(y))
                    {
                        erase_tree(1,n,1,siz[fin(x)]),erase_tree(1,n,1,siz[fin(y)]);
                        sum-=siz[fin(x)]*(siz[fin(x)]-1)/2,sum-=siz[fin(y)]*(siz[fin(y)]-1)/2;
                        siz[fin(x)]=siz[fin(x)]+siz[fin(y)],sum+=siz[fin(x)]*(siz[fin(x)]-1)/2;
                        add_tree(1,n,1,siz[fin(x)]),uni(x,y);
                        num--;
                    }
                    sum--;
                }
                else
                {
                    long long k;
                    scanf("%lld",&k);
                    printf("%lld %lld
    ",max(1ll,(num-k)),num+1-q_tree(1,n,1,k-sum,0));                
                }
            }
        }
        return 0;
    }
    
  • 相关阅读:
    tableview 与 tableview cell
    swift基础知识
    HttpRequest
    ios界面跳转
    C# TextBox常用方法总结
    C#中string.format用法详解
    数据库填充DataSet,逐行访问
    C#连接SQL SERVER数据库的详细步骤!
    高德地图API INVALID_USER_SCODE问题以及keystore问题
    基础地图Android SDK
  • 原文地址:https://www.cnblogs.com/megalovania/p/10792903.html
Copyright © 2011-2022 走看看