CF1994D-鸽巢原理

CF1994D-鸽巢原理

大致题意

Vanya 有一个图,图中有 n 个顶点(编号从 1 到 n )和 a 个由 n 个整数组成的数组;最初,图中没有边。万尼亚觉得无聊,为了找点乐子,他决定进行 n−1 次运算。

操作数 x (操作数从 1 开始依次编号)如下:

  • 选择 2 个不同的数 1≤u,v≤n ,使得$ |a_u−a_v| $可以被 x 整除。
  • 在顶点 u 和 v 之间添加一条无向边。

使用 n−1 运算帮助Vanya得到一个连通的图,或者确定这是不可能的。

  • 如果沿着边可以从任何顶点到达其他顶点,那么这个图就叫做连通图。

解题思路

首先介绍鸽巢原理

鸽巢原理:将n+1个物品划分为n组,那么至少有一组有两个(及以上)的物品

推广:将n个物品划分为k组,那么至少有一组有\(\lceil{\frac{n}{k}}\rceil\)个物品

对于本题,我们考虑操作的条件:$|a_u−a_v|%x==0 $,对于两个数如果他们对同一个数字取模余数相同,那么就会满足该条件

于是我们可以考虑将所有数字对操作数x取模,由于对于操作数1来说它的可使用边是最多的,而操作n-1使用边是最少的,所以我们优先逆序操作,将所有数字按照对x取余分组

根据鸽巢原理,至少有一组中包含两个或两个以上的数字,那么我们就可以将这两个数字进行连边,连完后需要删除其中一个数。

代码实现

#include<bits/stdc++.h>
using namespace std;
#define endl "\n"
void solve()
{
	int n;
	cin>>n;
	vector<int> a(n+1),vis(n+1);
	vector<pair<int,int>> edg;
	for(int i = 1;i <= n;++i) cin>>a[i];
	for(int i = n-1;i>=1;--i)
	{
		vector<vector<int>> vec(i);
		for(int j = 1;j<=n;++j)
		{
			if(!vis[j])
			{
				vec[a[j]%i].push_back(j);
			}
		}
		for(int j = 0;j < i;++j)
		{
			if(vec[j].size()>=2)
			{
				auto u = vec[j][0],v = vec[j][1];
				edg.push_back({u,v});
				vis[v] = 1;
				break;
			}
		}
	}

	cout<<"YES"<<endl;
	reverse(edg.begin(),edg.end());
	for(auto [u,v]:edg)
	{
		cout<<u<<" "<<v<<endl;
	}
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	int tt;
	cin>>tt;
	while(tt--)solve();
	return 0;
} 
posted @ 2024-08-05 18:12  empty_y  阅读(20)  评论(0)    收藏  举报