Codeforces Round #709 (Div. 2, based on Technocup 2021 Final Round)-C. Basic Dip

40 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

@TOC

Технокубок 2021 - Финал-C. Basic Diplomacy

传送门 Time Limit: 2 seconds Memory Limit: 512 megabytes

Problem Description

Aleksey has nn friends. He is also on a vacation right now, so he has mm days to play this new viral cooperative game! But since it's cooperative, Aleksey will need one teammate in each of these mm days.

On each of these days some friends will be available for playing, and all others will not. On each day Aleksey must choose one of his available friends to offer him playing the game (and they, of course, always agree). However, if any of them happens to be chosen strictly more than m2\left\lceil\dfrac{m}{2}\right\rceil times, then all other friends are offended. Of course, Aleksey doesn't want to offend anyone.

Help him to choose teammates so that nobody is chosen strictly more than m2\left\lceil\dfrac{m}{2}\right\rceil times.

Input

Each test contains multiple test cases. The first line contains the number of test cases tt (1t100001 \le t \le 10\,000). Description of the test cases follows.

The first line of each test case contains two integers nn and mm (1n,m1000001\leq n, m\leq 100\,000) standing for the number of friends and the number of days to play, respectively.

The ii-th of the following mm lines contains an integer kik_i (1kin1\leq k_i\leq n), followed by kik_i distinct integers fi1f_{i1}, ..., fikif_{ik_i} (1fijn1\leq f_{ij}\leq n), separated by spaces — indices of available friends on the day ii.

It is guaranteed that the sums of nn and mm over all test cases do not exceed 100000100\,000. It's guaranteed that the sum of all kik_i over all days of all test cases doesn't exceed 200000200\,000.

Output

Print an answer for each test case. If there is no way to achieve the goal, print "NO".

Otherwise, in the first line print "YES", and in the second line print mm space separated integers c1c_1, ..., cmc_m. Each cic_i must denote the chosen friend on day ii (and therefore must be one of fijf_{ij}).

No value must occur more than m2\left\lceil\dfrac{m}{2}\right\rceil times. If there is more than one possible answer, print any of them.

Sample Input

2
4 6
1 1
2 1 2
3 1 2 3
4 1 2 3 4
2 2 3
1 3
2 2
1 1
1 1

Sample Onput

YES
1 2 1 1 2 3 
NO

题目大意

AlekseyAleksey要与nn个朋友度过mm天。 一天只与一个朋友玩,一个朋友累计玩耍次数不得超过m2\left\lceil\dfrac{m}{2}\right\rceilm2\dfrac{m}{2}向上取整) 给你每天可以玩耍的朋友列表,问你能不能找到一种玩耍方法符合上述条件。


解题思路

贪心。关键是如何贪。

首先给你了mm天的朋友列表,每天的可玩人数不同。可玩人数少的最难满足,顾先满足人数少的那些天。 这就需要第一次排序:按可玩朋友数量,对不同的天数进行排序。

比如样例1:

4 6
1 1
2 1 2
3 1 2 3
4 1 2 3 4
2 2 3
1 3

共有44个朋友,共需要玩mm天。 第一天有1个朋友,编号为1 第二天有2个朋友,编号为1、2 \cdots 第6天有1个朋友,编号为3

初始状态:

1
12
123
1234
23
3
按每天的朋友数量进行排序:
1
--------
3
12
23
123
1234

注意,题目可没说给你的朋友序号是按从小到大给的,所以他可能给你个2 1 3之类的,但是不影响。

然后开始贪心。

  • 假如某天有朋友1 3 4,而1已经出现了9次了,3只出现了3次,4出现了5次。 那么首选肯定用3,因为3出现过的次数少。

  • 如果1已经出现了9次了,3只出现了3次,而4也出现了3,那么43选谁呢?继续观察,发现这天之后(已排序)3还能再出现2次,而4不会再出现了。 那么肯定首选4,因为不选白不选,选了后面也不会再出现了。

因此,我们得到了对于具体的某一天的优先方法:可用次数多的优先(then 后面出现次数少的优先,这个可有可无,无所谓了)。


技巧

  • 数据量较大,无法开 [1e5] [1e5]的数组。故用vector<vector<int> >来存。
  • 第一次排序会打乱原有的天数,故在每天的vector的末尾,插入一个数,来记录这个vector是那一天的。排序之后再取出。

AC代码

/*
 * @Author: LetMeFly
 * @Date: 2021-03-21 22:34:14
 * @LastEditors: LetMeFly
 * @LastEditTime: 2021-03-22 21:07:59
 */
#include<bits/stdc++.h>
using namespace std;
#define mem(a) memset(a,0,sizeof(a))
#define dbg(x) cout<<#x<<" = "<<x<<endl
#define fi(i,l,r) for(int i=l;i<r;i++) 
#define cd(a) scanf("%d",&a)
typedef long long ll;
bool cmp1(vector<int>&a,vector<int>&b)//根据每天的朋友列表的人数进行排序
{
	if(a.size()!=b.size())//人数少的优先
		return a.size()<b.size();
	for(int i=0;i<a.size();i++)//人数相同的情况下,编号小的优先
	{
		if(a[i]!=b[i])
			return a[i]<b[i];
	}
	return 1;
}
int HouMianChuXian[200010];//HouMianChuXian[i]表明i在后面的天数中还会出现多少次
int KeYongCiShu[200010];//KeYongCiShu[i]表示i还可以再出现几次
int ans[200010];//答案。因为第一次排序打乱了天数,故答案不是按顺序得到的,需要先记录一下
bool cmp2(int a,int b)//对于具体的某一天,根据贪心原则进行排序
{
	if(KeYongCiShu[a]!=KeYongCiShu[b])//可以出现的次数不相同
		return KeYongCiShu[a]>KeYongCiShu[b];//还可以出现的次数多的优先
	if(HouMianChuXian[a]!=HouMianChuXian[b])
		return HouMianChuXian[a]<HouMianChuXian[b];//其实cmp2可以简化,简化版本的cmp2见下方
	if(a!=b)//上述条件全部相同,按编号进行排序(非必要)
		return a<b;
	return 1;
}
int main()
{
	int N;
	cin>>N;
	while(N--)//N组测试样例
	{
		int n,m;
		scanf("%d%d",&n,&m);
		int ZuiDuoYueJiCi=m/2+m%2;//最多出现的次数
		for(int i=1;i<=n;i++)
		{
			HouMianChuXian[i]=0;//初始化,后面出现的次数为0
			KeYongCiShu[i]=ZuiDuoYueJiCi;//初始化,还可以出现的次数为最多出现的次数
		}
		vector<vector<int> >a;//2维vector
		for(int i=0;i<m;i++)//m天的每一天
		{
			vector<int>thisVector;//今天的vector
			int thisFriend;//这次出现的朋友
			int k;
			scanf("%d",&k);//今天有k个朋友
			for(int j=0;j<k;j++)
			{
				scanf("%d",&thisFriend);//这个朋友是thisFriend
				HouMianChuXian[thisFriend]++;//后面出现thisFriend的次数+1
				thisVector.push_back(thisFriend);//插入vector
			}
			thisVector.push_back(i+1);//tips: 尾部插入今天的天数,以便排序后使用
			a.push_back(thisVector);//把今天的vector插入到总的大vector中
		}
		sort(a.begin(),a.end(),cmp1);//第一次排序,根据当天出现的人数进行排序
		bool cannot=0;//flag,不行吗? 默认false
		for(int i=0;i<m;i++)//共有m天
		{
			vector<int>thisDay=a[i];//要处理的这一天的vector
			vector<int>::iterator it=thisDay.end();
			it--;
			int thisDayNum=*it;//获得排序前这天是第几天
			thisDay.erase(it);//去掉最后一个代表天数的数字
			sort(thisDay.begin(),thisDay.end(),cmp2);//对具体的这一天,根据优先级进行排序
			for(int j=0;j<thisDay.size();j++)
			{
				HouMianChuXian[thisDay[j]]--;//这天过后,这天出现的每个朋友再次出现的次数-1
			}
			int thisDaysFriend=thisDay[0];//选择最为优先的朋友
			KeYongCiShu[thisDaysFriend]--;//这个朋友还可以出现的次数-1
			if(KeYongCiShu[thisDaysFriend]<0)//如果这个朋友还可以出现的次数已经为负了
			{
				cannot=1;//不行
			}
			ans[thisDayNum]=thisDaysFriend;//记录下答案:当天的选择的朋友
		}
		if(cannot)//如果不行
		{
			puts("NO");
		}
		else//否则可以
		{
			puts("YES");
			for(int i=1;i<=m;i++)
			{
				printf("%d ",ans[i]);//输出答案:每天选择的朋友
			}
			puts("");
		}
		
	}
	return 0;
}

cmp2简单版本:

bool cmp2(int a,int b)//对于具体的某一天,根据贪心原则进行排序
{
	if(KeYongCiShu[a]!=KeYongCiShu[b])//可以出现的次数不相同
		return KeYongCiShu[a]>KeYongCiShu[b];//还可以出现的次数多的优先
	return 1;
}

同步发文于我的CSDN,原创不易,转载请附上原文链接哦~
Tisfy:blog.csdn.net/Tisfy/artic…