2019 第十六届浙江省赛部分题解

A:Vertices in the Pocket

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,bi≤n, ai≠bi), 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≤ki≤n(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

题意:开始有n个独立节点,有两种操作,第一种操作:将点x和y连接;第二种操作:询问在将图内添加m条边后,联通块最少和最多的个数。

方法:最少的个数很好求,就是当前剩下的联通块减去添加的边的条数和1取较大值。而最多的个数就复杂了很多了。我知道的方法是线段树加二分。建立一棵树,叶子节点的编号表示联通块的大小,节点内存储大小为k的块的个数,可容纳的边的条数还有点的个数。最后查询的时候进行二分。但是这样我们二分到叶子节点k时就不知道要合并掉多少个大小为k的块了,所以我们在子叶子节点这里再做一次二分,这样复杂度仍然只有一个log2(n),因为每次查询我们只会到一个子叶节点。

#include <bits/stdc++.h>
#define mid (l+r)/2
using namespace std;
const int maxn=1e5+5;
int n,m,father[maxn],num[maxn];
struct node
{
	int point_num;//子树的点的个数之和
	long long edeg_num;//可容纳边的条数
	int num;//子树的块的个数
}no[maxn<<2];
void pushup(int cur)
{
	no[cur].edeg_num = no[cur<<1].edeg_num + no[cur<<1|1].edeg_num;
	no[cur].num = no[cur<<1].num+ no[cur<<1|1].num;
	no[cur].point_num = no[cur<<1].point_num + no[cur<<1|1].point_num;
}
int find(int x)
{
	return father[x]==x ? x : father[x]=find(father[x]);
}
void merge(int x,int y)
{
	father[find(y)]=find(x);
}
void bulid(int l,int r,int loc)
{
	if(r==1)
	//此时一定到了叶子节点了,并且此叶子节点的编号为1
	{
		no[loc].edeg_num=0;
		no[loc].num = no[loc].point_num=n;
		return;
		//表示大小为1的联通块的个数有n个,故子树的块数为n
	}
	else
	{
		no[loc].edeg_num = no[loc].num = no[loc].point_num=0;
		//其他大小的块个数为0
	}
	if(l != r)
	{
		bulid(l,mid,loc<<1);//宏定义了mid
		bulid(mid+1,r,loc<<1|1);
	}
}
void add(int l,int r,int loc,int x)
{
	if(l==r)
	{
		no[loc].point_num+=l;//添加了一个含有x个点的联通块
		no[loc].edeg_num+=l*(l-1)/2;//x点最多可容纳的边的条数
		no[loc].num++;//块的个数加一
		return;
	}
	if(x<=mid)
	{
		add(l,mid,loc<<1,x);
	}
	else
	{
		add(mid+1,r,loc<<1|1,x);
	}
	pushup(loc);
}
void erase(int l,int r,int loc,int x)
{
	if(l==r)
	{
		no[loc].point_num-=l;//删除了一个含有x个点的联通块
		no[loc].edeg_num-=l*(l-1)/2;//x点最多可容纳的边的条数
		no[loc].num--;//块的个数减一
		return;
	}
	if(x<=mid)
	{
		erase(l,mid,loc<<1,x);
	}
	else
	{
		erase(mid+1,r,loc<<1|1,x);
	}
	pushup(loc);
}
int query(int l,int r,int loc,long long k,long long v)
{
	if(v > n)exit(0);
	if(k<=0)return 1;
	if(l==r)
	{
		int ll=1,rr=no[loc].num;//二分合并块的个数
		while(ll < rr)
		{
			int mmid=(ll+rr)>>1;
			if((v+mmid*l)*(v+mmid*l-1)/2 >= l*(l-1)/2*mmid+k)rr=mmid;
			else ll=mmid+1;
		}
		return ll;
	}
	else
	{
		//优先合并右子树,因为右子树的块比左子树的块大,需要融合的块会少一些
		if((v+no[loc<<1|1].point_num)*(v+no[loc<<1|1].point_num-1)/2 >= k+no[loc<<1|1].edeg_num)
			return query(mid+1,r,loc<<1|1,k,v);
		else
			return query(l,mid,loc<<1,k+no[loc<<1|1].edeg_num,v+no[loc<<1|1].point_num)+no[loc<<1|1].num;
	}
}
int main()
{
	scanf("%*d");
	while(scanf("%d %d",&n,&m)==2)
	{
		for(int i=1;i<=n;i++)
		{
			father[i]=i;
			num[i]=1;
		}
		long long max_edge=0;//在不减少联通块的情况下能添加的最多边数
		int ans=n;//联通块的个数
		bulid(1,n,1);
		while(m--)
		{
			int op;
			scanf("%d",&op);
			if(op==1)
			{
				int x,y;
				scanf("%d %d",&x,&y);
				int fx=find(x),fy=find(y);
				if(fx != fy)
				{
					erase(1,n,1,num[fx]);//删除一个点的个数为num[fx]的块
					erase(1,n,1,num[fy]);
					max_edge -= num[fx]*(num[fx]-1)/2;
					max_edge -= num[fy]*(num[fy]-1)/2;
					num[fx] = num[fx]+num[fy];
					max_edge += num[fx]*(num[fx]-1)/2;
					add(1,n,1,num[fx]);
					merge(x,y);
					ans--;
				}
				max_edge--;
			}
			else
			{
				long long x;
				scanf("%lld",&x);
				int ww=query(1,n,1,x-max_edge,0);
				printf("%d %d\n",max(1ll,ans-x),ans+1-ww);
			}
		}
	}
}

B:Element Swapping

DreamGrid has an integer sequence a1,a2,…,an and he likes it very much. Unfortunately, his naughty roommate BaoBao swapped two elements ai and aj (1≤i<j≤n) in the sequence when DreamGrid wasn't at home. When DreamGrid comes back, he finds with dismay that his precious sequence has been changed into a1,a2,…ai−1,aj,ai+1,…,aj−1,ai,aj+1,…,an!

What's worse is that DreamGrid cannot remember his precious sequence. What he only remembers are the two values

x=∑k=1nkakandy=∑k=1nka2k

Given the sequence after swapping and the two values DreamGrid remembers, please help DreamGrid count the number of possible element

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值