ZOJ-4100 Vertices in the Pocket 2019浙江省省赛

题目链接

Vertices in the Pocket


Time Limit: 2 Seconds      Memory Limit: 65536 KB


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

  • 1 a b -- Connect the a-th vertex and the b-th vertex by an edge. It's guaranteed that before this operation, there does not exist an edge which connects vertex a and b directly.
  • 2 k -- Find the answer for the query: What's the minimum and maximum possible number of connected components after adding k new edges to the graph. Note that after adding the k 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 T, indicating the number of test cases. For each test case:

The first line contains two integers n and q (1≤n≤105, 1≤q≤2×105), indicating the number of vertices and the number of operations.

For the following q lines, the i-th line first contains an integer pi (pi∈{1,2}), indicating the type of the i-th operation.

  • If pi=1, two integers ai and bi follow (1≤ai,bin, aibi), indicating an operation of the first type. It's guaranteed that before this operation, there does not exist an edge which connects vertex a and b directly.
  • If pi=2, one integer ki follows (0≤kin(n−1)2), indicating an operation of the second type. It's guaranteed that after adding ki edges to the graph, the graph is still possible to be a simple graph.

 

It's guaranteed that the sum of n in all test cases will not exceed 106, and the sum of q in all test cases will not exceed 2×106.

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

题解:权值线段树+二分+并查集。

解释写在代码里。

 

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int pre[maxn],Count[maxn];
int n,m;
long long sum,num;
int find(int x)//并查集操作 
{
	return x==pre[x]?x:pre[x]=find(pre[x]);
}
struct node
{
	long long poi;//该区间内点数总和
	long long edge;//该区间内可容纳的边数
	long long piece;//该区间的连通块个数
} sz[maxn<<2];
void pushup(int rt) 
{
	sz[rt].poi=sz[rt<<1].poi+sz[rt<<1|1].poi;
	sz[rt].edge=sz[rt<<1].edge+sz[rt<<1|1].edge;
	sz[rt].piece=sz[rt<<1].piece+sz[rt<<1|1].piece;
}
void add_tree(int l,int r,int rt,int pos)
{
	if(l==r)//权值线段树建树 
	{
		sz[rt].poi+=l;//该叶子节点的点数 
		sz[rt].edge+=((l)*(l-1))>>1;//该叶子节点的边数 
		sz[rt].piece+=1;//该叶子节点的边块数 
	}
	else
	{
		int mid=(l+r)>>1;
		if(mid>=pos)
			add_tree(l,mid,rt<<1,pos);
		else if(mid<pos)
			add_tree(mid+1,r,rt<<1|1,pos);
		pushup(rt);
	}
}
void erase_tree(int l,int r,int rt,int pos)
{
	if(l==r)
	{
		sz[rt].poi-=l;
		sz[rt].edge-=(l*(l-1))>>1;
		sz[rt].piece-=1;
	}
	else
	{
		int mid=(l+r)>>1;
		if(mid>=pos)
			erase_tree(l,mid,rt<<1,pos);
		else if(mid<pos)
			erase_tree(mid+1,r,rt<<1|1,pos);
		pushup(rt);
	}
}
int get_tree(int l,int r,int rt,long long k,long long v)//k代表需要添加的边数,v是已经获得的点数 
{
	if(l==r)
	{
		int ll=1;
		int rr=sz[rt].piece;//最少1块,最多rr块 
		while(ll<rr)//二分连通块 
		{
			int mid=(ll+rr)>>1;
			if((v+mid*l)*(v+mid*l-1)/2>=l*(l-1)/2*mid+k)
			{
				rr=mid;
			}
			else
				ll=mid+1;
		}
		return ll;
	}
	else
	{
		int mid=(l+r)>>1;
		if((v+sz[rt<<1|1].poi)*(v+sz[rt<<1|1].poi-1)/2>=k+sz[rt<<1|1].edge)
		{
			return get_tree(mid+1,r,rt<<1|1,k,v);//如果合并右边可以做到满足条件 
		}
		else//如果合并右边还不能满足条件,则把右边的连通块全部加上,然后合并左边 
		{
			//合并左边的时候,已经知道单纯的合并右边还不能满足添加x条边的要求,
			//那么我们把右边的边加上x作为我们要合并的边(此时边数变多了)。
			//然后我们的获得的点v就可以加上右边的点数了。 
			//那么此时我们的点数就是左边的点加上右边的点,假设为n。
			//于是我们现在就可以在不添加连通块的情况下添加(n*(n-1))/2条边了 
			return get_tree(l,mid,rt<<1,k+sz[rt<<1|1].edge,v+sz[rt<<1|1].poi)+sz[rt<<1|1].piece;
		}
	}

}
void build(int l,int r,int rt)
{
	if(l==r&&l==1)//第一个叶子节点有n个点,0条边,n个连通块 
		sz[rt].poi=sz[rt].piece=n,sz[rt].edge=0;
	else//其他叶子节点建树时 没有点 没有边 没有连通块 
		sz[rt].poi=sz[rt].piece=sz[rt].edge=0;
	if(l!=r)
	{
		int mid=(l+r)>>1;
		build(l,mid,rt<<1);
		build(mid+1,r,rt<<1|1);
	}
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d %d",&n,&m);
		for(int i=1; i<=n; i++)
		{
			pre[i]=i;//并查集节点
			Count[i]=1;//代表一个集合中有多少个点,开始都是独立的点,初始为1
		}
		sum=0;//sum代表 不增加连通块可以加的边数 
		num=n;//连通块的点数 
		build(1,n,1);
		for(int i=1; i<=m; i++)
		{
			int op;
			scanf("%d",&op);
			if(op==1)
			{
				int x,y;
				scanf("%d %d",&x,&y);
				int fa=find(x);
				int fb=find(y);
				if(fa!=fb)//如果添加的一条边没在同一个连通块内 
				{
					erase_tree(1,n,1,Count[fa]);
					erase_tree(1,n,1,Count[fb]);//删除 
					sum-=(Count[fa]*(Count[fa]-1))>>1;//减去Count[fa]内最多可以容纳的边数 
					sum-=(Count[fb]*(Count[fb]-1))>>1;//减去Count[fb]内最多可以容纳的边数 
					Count[fa]=Count[fa]+Count[fb];
					sum+=Count[fa]*(Count[fa]-1)/2;//加上把两个合并之后最多能容纳多少边数 
					add_tree(1,n,1,Count[fa]);//建一个有Count[两个集合之和点数]的树 
					pre[fb]=fa;//并查集处理 
					num--;//连通块数量减一 
				}
				sum--;
			}
			else
			{
				//	cout<<"num="<<num<<endl;
				long long x;
				scanf("%lld",&x);
				printf("%lld %lld\n",max(1ll,(num-x)),num+1-get_tree(1,n,1,x-sum,0ll));
			}
		}
	}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值