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;
}
标签:操作数,int,CF1994D,vector,vec,鸽巢,原理
From: https://www.cnblogs.com/empty-y/p/18343733