zoj4100 Vertices in the Pocket (浙江省赛A题,二分 + 线段树)

2 篇文章 0 订阅
2 篇文章 0 订阅

Vertices in the Pocket


Time Limit: 2 Seconds      Memory Limit: 65536 KB


DreamGrid has just found an undirected simple graph with  vertices and no edges (that's to say, it's a graph with  isolated vertices) in his right pocket, where the vertices are numbered from 1 to . Now he would like to perform  operations of the following two types on the graph:

  • 1 a b -- Connect the -th vertex and the -th vertex by an edge. It's guaranteed that before this operation, there does not exist an edge which connects vertex  and  directly.
  • 2 k -- Find the answer for the query: What's the minimum and maximum possible number of connected components after adding  new edges to the graph. Note that after adding the  edges, the graph must still be a simple graph, and the query does NOT modify the graph.

 

Please help DreamGrid find the answer for each operation of the second type. Recall that a simple graph is a graph with no self loops or multiple edges.

Input

There are multiple test cases. The first line of the input is an integer , indicating the number of test cases. For each test case:

The first line contains two integers  and  (, ), indicating the number of vertices and the number of operations.

For the following  lines, the -th line first contains an integer  (), indicating the type of the -th operation.

  • If , two integers  and  follow (, ), indicating an operation of the first type. It's guaranteed that before this operation, there does not exist an edge which connects vertex  and directly.
  • If , one integer  follows (), indicating an operation of the second type. It's guaranteed that after adding  edges to the graph, the graph is still possible to be a simple graph.

 

It's guaranteed that the sum of  in all test cases will not exceed , and the sum of  in all test cases will not exceed .

Output

For each operation of the second type output one line containing two integers separated by a space, indicating the minimum and maximum possible number of connected components in this query.

Sample Input

1
5 5
1 1 2
2 1
1 1 3
2 1
2 3

Sample Output

3 3
2 3
1 2

题意:

现在有1到n个不连通的块,起始是点,现在有两个操作

1:对于1和2点连一条边

2:给一个k,查询在给这些点加k条边,最多可以有多少个联通块和最少可以有多少个联通块

思路:

其实很容易可以看出来,对于2操作,找到最小的连通块很好找,一 条边最多可以减少一个连通块(当然如果都联通的话那就不能减少了)

那找最多的就是在当前所有联通块可以变成完全图的基础上,加上一些边,如果k超过这个边,那就合并两个最大的块,如果k还有,然后再和后面的合并,其实我们可以考虑用一个堆来维护这些块,但是他强制在线了,会建边,所以我们需要用一个在线的数据结构,所以线段树就可以来解决这个问题了。

考虑搞一个权值线段树来保存连通块有x个点的块数和点数还有平方和(这些在求2操作的时候都会用到,因为算还需要多少个边可以成为完全图的时候需要用到,仔细想一下就可以),1操作就是线段树单点更新,比如合并a和b就是找到a和b的块--,然后a + b的点数块 ++ ,至于2操作,我们可以在树上二分一下,类似主席树找区间第k大,就是否能构成完全图所需要的边数二分,最后假如我们找到了一个块x,但是这个块其实是有num个块的,我们不知道我们需要多少个x的块数可以很好的达到我们的k,所以在最后找到x的时候还需要在二分一下,我们需要当前块的多少个块,记录一下结果(另外并查集需要改成非递归的)

代码:

int fa[maxn];
int Find(int x) {    	int p=x;
	while(p!=fa[p])
		p=fa[p];
	while(x!=fa[x]){
		x=fa[x];
		fa[x]=p;
	}
	return p;}
int n,m;
int x,y,k;
ll cntt,cnt,num[maxn];
struct node{
    ll l,r;
    ll pointnum,num;ll sum = 0;
    ///点数、       个数
}root[maxn * 4];
void build(int id,int L,int R)
{
    root[id].l=L;
    root[id].r=R;
    if(L==R){
        root[id].pointnum =  root[id].sum = root[id].num = (L==1?n:0); return;
    }
    int mid=(L+R)>>1;
    build(id<<1,L,mid);
    build(id<<1|1,mid+1,R);
    root[id].pointnum=root[id<<1].pointnum+root[id<<1|1].pointnum;
    root[id].num=root[id<<1].num+root[id<<1|1].num;
    root[id].sum=root[id<<1].sum+root[id<<1|1].sum;
}
void update(int id,ll t,ll val)
{
    if(root[id].l==root[id].r)
    {
//        root[id].s += val;//个数加一
        root[id].pointnum += t * val;
        root[id].num += val;
        root[id].sum += val * t * t;
        return;
    }
    int mid=(root[id].l+root[id].r)>>1;
    if(t<=mid) update(id<<1,t,val);
    else update(id<<1|1,t,val);
    root[id].pointnum=root[id<<1].pointnum+root[id<<1|1].pointnum;
    root[id].num=root[id<<1].num+root[id<<1|1].num;
    root[id].sum=root[id<<1].sum+root[id<<1|1].sum;
}
int query(int id,ll k,ll temp)
{
//    cout << id << endl;
    if(root[id].l==root[id].r){
        ll l = 0,r = root[id].num;
        ll ans = 0;
        while(l <= r){
            ll mid = (l + r) / 2;
//            cout << "sdada" << endl;
    ///      (m * n) * (n * m - 1) / 2 - m * (n * (n - 1) / 2) - k
//            if(mid*(mid-1)/ 2*tree[id].l*tree[id].l + mid*tree[id].l*sum >= k) r=mid;
            if(mid*(mid-1)/2*root[id].l*root[id].l + mid*root[id].l*temp >= k){
//            if(root[id].pointnum * (root[id].pointnum
//            - 1) / 2 - (mid * (root[id].pointnum / root[id].num * (root[id].pointnum / root[id].num - 1) / 2)) - k >= 0 ){
               ans = mid;
               r = mid - 1;
            }else l = mid + 1;
        }
        return ans;
    }
    int mid = (root[id].l + root[id].r) / 2;
    ll tmp = (root[id * 2 + 1].pointnum * root[id * 2 + 1].pointnum - root[id * 2 + 1].sum) / 2 + root[id * 2 + 1].pointnum * temp ;
	if(tmp < k)
		return root[id * 2 + 1].num + query(id * 2,k - tmp,temp + root[id * 2 + 1].pointnum);
	else return query(id * 2 + 1,k,temp);
//    ll sum = (root[id * 2].pointnum) * ((root[id * 2].pointnum) - 1) / 2
//    -  root[id * 2].pointnum / root[id * 2].num * (root[id * 2].pointnum / root[id * 2].num - 1) / 2 * root[id * 2].num;
//    cout << sum << " === " << id << endl;
//    if(sum >= k) return root[id * 2].num + query(id * 2 + 1,k - sum);
//    else return  query(id * 2,sum);
}
ll getMin(ll x){
    if(x >= cnt) return 1;
    else return cnt - x;
}
ll getMax(ll x){
    if(cntt >= x) return cnt;
    x -= cntt;
//    cout << "dsada" << endl;
    ll num = query(1,x,0);
//    cout << x << " *** ---- " << num << endl;
//    cout << cnt - num + 1 << endl;
    return cnt - num + 1;
}
int main()
{
     int  t = read();
     while(t--){
        n = read(),m = read();
        cnt = n;
        for(int i = 1;i <= n;i++) fa[i] = i,num[i] = 1;
        build(1,1,n);
        cntt = 0;
        while(m--){
            int op = read();
            if(op == 1){
                int x = read(),y = read();
                 int X = Find(x);
                 int Y = Find(y);
                 if(X != Y){
                    if(num[X] < num[Y]) swap(X,Y);
                    fa[Y] = X;
                    update(1,num[X],-1);
                    update(1,num[Y],-1);
                    cntt += num[X] * num[Y] - 1;
                    num[X] += num[Y];
                    num[Y] = 0;
                    cnt--;
                    update(1,num[X],1);
                 }else{
                    cntt--;
                }
//                for(int i = 1;i <= n;i++){
//                   cout << i << " ==  " << root[i].num << "  " << root[i].sum << "  " << root[i].pointnum << endl;;
//                }
            }else{
//                cout << root[1].num << endl;
//                cout << cntt << " **** " <<endl;
                ll k = read();
                printf("%lld %lld\n",getMin(k),getMax(k));
//                cout <<  << " ";
//                cout <<  << endl;
            }
        }
     }
     return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值