Codeforces Round #708 (Div. 2)-B题题解-简单数论取模

原题链接:

https://codeforces.com/contest/1497/problem/B

简单来说就是将数列里的数进行分成几组,使每组中相邻的两个数加起来能被m整除,输出最少能分成几组。

我们将每个数取余,并开一个专门用来记录每种余数个数的数组c[]。比如第一个样例的数列:2 2 8 6 9 4,m = 4。
注意到2%4 = 2, 6%4 = 2,而这组数列共有3个数余数为2,依次计数有c[2] = 3。
为了方便我们每次读入一个数就将它的余数记录:

for (int i=1;i<=n;i++){
				cin>>a[i];
				c[a[i]%m]++;
				a[i] = 0;
			}


最后将a[]赋值为0。因为之后不再需要用到这个数组,我们只需要专门对c[]进行分类讨论和计数就好。

假设一个数的余数为i,而另一个数余数为m-i,那么它们相加的和一定能被m整除。

如果没有数能和它相加为0,那么一个数为一组。

如果一个数的余数为0,那么他们必定能分在一个组。

同时如果一个数的余数为m/2,那么这一组相邻两个数的和一定也能被m整数。

所以对0进行特判,只要他们各自的余数总计不为0,就一定有一组:

if (c[0]) num++;

(不对m/2进行特判,因为和接下来的分类讨论实质效果是一样的,可以自己模拟一下)

所以我们从余数为1和余数m-1一个一个遍历:

if (c[0]) num++;//余数为0的只要有数 必为一组	
			for (int i=1;i<m;i++){
				//printf("c[%d] = %d\n",i,c[i]); 
				if (!c[i]&&!c[m-i]) continue;//如果c[i]和c[m-i]都为0,直接开始下一次循环 
				if (c[i]&&c[m-i]){ //如果都不为0 
					if (abs(c[i]-c[m-i])<=1) 
						num++,c[m-i]=0;
					else num += abs(c[i]-c[m-i]),c[m-i]=0;
				} 
				else if(c[i]&&!c[m-i) num +=c[i];//如果c[i]不为0,但c[m-i]为0,直接将其加起来 
				//printf("num = %d\n",num);
			}


可能会有读者对中间的if语块判断abs(c[i]-c[m-i])是否小于等于1的操作产生疑问,我们来举个例子。

在第二组样例中: 1 1 1 5 2 4 4 8 6 7  m = 8

通过计数,c[1] = 3, c[7] = 1。

我们要将余数为1和余数为7的数进行组合(题目中正好是1和7)
显然可以注意到,一组放进 1 7 1,而另一组单独放 1,是最少的组数

那么当c[1] = 3,c[7] = 2呢?很高兴的,我们可以这么排列:
1 7 1 7 1 
那么只需要一组就好了。

同样的,当c[1] = 3,c[7] = 3,也只需要一组:
1 7 1 7 1 7 (或 7 1 7 1 7 1)

下面两种和第一种有什么区别的?可以观察到,当c[1]和c[7]组合的时候,我们要将数量较小的7放在两个1中间,就能在满足相邻两数相加的余数为0时“节省”组数。

而一个7两边当然只能放两个1,也就是说当abs(c[1]-c[7])>=2 时,所有7两边的位置已经放满了,多余的1只能各自另为一组。

完整代码:

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int t;
const int maxn = 1e5 + 7;
int a[maxn]; 
int c[maxn];
int main(){
	cin>>t;
	while (t--){
		int n,m;
		int num=0;
		cin>>n>>m;
		if (n==1) cin>>a[1],a[1]=0,cout<<1<<endl;
		else{ 
			for (int i=1;i<=n;i++){
				cin>>a[i];
				c[a[i]%m]++;
				a[i] = 0;
			}
			if (c[0]) num++;//余数为0的只要有数 必为一组	
			for (int i=1;i<m;i++){
				//printf("c[%d] = %d\n",i,c[i]); 
				if (!c[i]&&!c[m-i]) continue;//如果c[i]和c[m-i]都为0,直接开始下一次循环 
				if (c[i]&&c[m-i]){ //如果都不为0 
					if (abs(c[i]-c[m-i])<=1) 
						num++,c[m-i]=0;
					else num += abs(c[i]-c[m-i]),c[m-i]=0;
				} 
				else if(c[i]&&!c[m-i) num +=c[i];//如果c[i]不为0,但c[m-i]为0,直接将其加起来 
				//printf("num = %d\n",num);
			}
			for (int i=0;i<=m;i++) c[i] = 0;
			
			cout<<num<<endl;
		} 
		//printf("................\n");
		} 
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值