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;
}