题干:
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 ;
}