题干:

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

解题报告:

以连通块中元素个数为权值,建立线段树,维护三个变量,分别是cnt(这样的连通块的数量),sum(连通块的总的点数),pfh(各连通块之间点数的平方和)(注意不是和的平方)

cnt代表连通块数量,tot_ret代表当前状态下的图,在不增长连通块个数的前提下,可以加的边数。

所以对于最小值很显然。

对于最大值,首先减去块内连的边,然后去线段树查询剩下的边怎么加,首先填连通块元素大小大的,这样也就是类似查询第k大,查到叶子结点,再判断 块中元素为tree[cur].l的 块 我需要多少个 返***去,就行了。

AC代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define F first
#define S second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 2e5 + 5;
int n,q,cnt,f[MAX];
ll tot_ret,ret[MAX],num[MAX];
int getf(int v) {
	return f[v] == v ? v : f[v] = getf(f[v]);
}
struct TREE {
	int l,r;
	ll cnt,sum;
	ll pfh;
} tree[MAX<<2];
void pushup(int cur) {
	tree[cur].cnt = tree[cur*2].cnt + tree[cur*2+1].cnt;
	tree[cur].sum = tree[cur*2].sum + tree[cur*2+1].sum;
	tree[cur].pfh = tree[cur*2].pfh + tree[cur*2+1].pfh;
}
void build(int l,int r,int cur) {
	tree[cur].l=l;tree[cur].r=r;
	if(l == r) {
		tree[cur].pfh = tree[cur].sum = tree[cur].cnt = (l==1?n:0);
		return;
	}
	int mid = (l+r)>>1;
	build(l,mid,cur*2);
	build(mid+1,r,cur*2+1);
	pushup(cur);
}
void update(int cur,ll tar,ll val) {
	if(tree[cur].l == tree[cur].r) {
		tree[cur].cnt += val;// 连通块元素数量为tar的连通块的数量。 
		tree[cur].sum += val*tar;
		tree[cur].pfh += val*tar*tar;
		return;
	}
	int mid = (tree[cur].l+tree[cur].r)>>1;
	if(tar<=mid) update(cur*2,tar,val);
	else update(cur*2+1,tar,val); 
	pushup(cur);
}
ll query(int cur,ll k,int sum) {
	if(tree[cur].l == tree[cur].r) {
		ll l=1,r=tree[cur].cnt,mid;
		mid=(l+r)>>1;
		while(l<r) {
			mid=(l+r)>>1;
			if(mid*(mid-1)/2*tree[cur].l*tree[cur].l + mid*tree[cur].l*sum >= k) r=mid;
			else l = mid+1;
		}
		return l;
	}
	int mid = (tree[cur].l+tree[cur].r)>>1;
	ll tmp = (tree[cur*2+1].sum * tree[cur*2+1].sum - tree[cur*2+1].pfh)/2 + tree[cur*2+1].sum * sum ;
	if(tmp < k)
		return tree[cur*2+1].cnt + query(cur*2,k-tmp,sum+tree[cur*2+1].sum);
	else return query(cur*2+1,k,sum);
}
ll cal(ll k) {
	if(k<=tot_ret) return cnt;
	k-=tot_ret;
	ll tmp = query(1,k,0);
	return cnt-tmp+1;
}
int main()
{
	int t;
	cin>>t;
	while(t--) {
		scanf("%d%d",&n,&q);
		for(int i = 1; i<=n; i++) f[i] = i,num[i] = 1,ret[i] = 0;
		tot_ret=0,cnt=n;//cnt记录连通块数 
		build(1,n,1);
		while(q--) {
			int op;
			scanf("%d",&op);
			if(op == 1) {
				int a,b;
				scanf("%d%d",&a,&b);
				a=getf(a),b=getf(b);
				if(a==b) {
					ret[a]--;
					tot_ret--;
					continue;
				}
				if(num[a] > num[b]) swap(a,b);//让b是边多的
				update(1,num[a],-1);
				update(1,num[b],-1);
				f[a]=b;//按秩合并到大集合
				tot_ret += num[a]*num[b]-1;cnt--;
				ret[b]=ret[a]+ret[b] + num[a]*num[b]-1;
				num[b]+=num[a];
				ret[a]=num[a]=0;
				update(1,num[b],1);			
			}
			else {
				ll k;
				scanf("%lld",&k);
				ll minn = max(1LL,cnt-k);
				ll maxx = cal(k);
				printf("%lld %lld\n",minn,maxx);
			}			
		}
	}
	return 0 ;
}