CF1714E Add Modulo 10#811(div.3)

题目链接

https://codeforces.com/problemset/problem/1714/E

题意简述

给你一个数组 \(a\) ,你可以对数组 \(a\) 中的任意一个元素进行任意次如下操作

  • \(a[i]=a[i]+a[i]\ \%\ 10\)

你需要判断这个数组是否能经过任意次操作后所有元素都相同,能则输出"YES",否则输出"NO"

题外话:这大概是写的目前为止最长的一篇题解了..(虽然题目不是很难)

样例

点击查看样例

样例过长且意义不大,此题不再给出.请听下面分析

分析

样例1

\(6\ \ \ 11\)

对于数 \(6\) ,我们进行操作后 , 结果为 \(6\ 12\ 14 \ 18 \ 26 ...\)
对于数 \(11\) ,我们进行操作后 ,结果为 \(11 \ 12\ 14\ 18\ 26 ...\)
所以是"YES"

样例2

\(2 \ \ \ \ 18 \ \ \ \ 22\)

对于数 \(2\) , 操作结果为 \(2\ 4\ 8\ 16\ 22\ 24\ 28\ 36\ 42\ ...\)
对于数 \(18\) , 操作结果为 \(18\ 26\ 32\ 34\ 38\ 46\ 52\ ...\)
对于数 \(22\) , 操作结果为 \(22\ 24\ 28\ 36\ 42\ ...\)

可以看到无论进行多少次操作都不会相等,所以为"NO"

稍加思索我们可以发现,除了 \(5\)\(0\) 结尾的数,其余数进行若干次操作后,末尾的数都是 \(2\ 4\ 8\ 6\)这样的循环,并且进行任意次操作后末尾的数都不会变为除此之外的数

并且我们发现进行一次 \(2\ 4\ 8\ 6\) 的循环节这个数会增大 \(20\). (分别在末尾由 \(8\) 变化到 \(6\) 时和由 \(6\) 变化为 \(2\) 时十位进 \(1\) ).

\(2\)\(22\)\(2\) 进行若干次操作后会加上 \(20\) 的整数倍,而 \(2\)\(18\) 无论两个数如何操作他们都相差 \(10\) ,这给了我们解题的灵感,对数组中元素的数进行化简操作,看看他们化简完后是否相差大于等于 \(10\). \(2\) 与 化简完后的 \(22\) \(24\) \(28\) 相差 \(10\)以内,考虑到进位操作, 虽然与 \(36\) \(42\) 相差大于 \(10\), 但是我们可以对 \(36\) 进一位或者去一位再化简成 \(2\) . 这一块可以先不用管,后面会讲到

何为化简 ? 就是如 \(22\) 可化简为 \(2\) , \(38\) 化简为 \(18\) 等等等等.就是这个数字中有多少个 \(20\) 就减去多少个 \(20\) .然而因为进位这个机制的存在,我们并不能把 \(38\) 化简为 \(18\) 就完事了,应该把他化简为 \(8\) .\(8\)才是 \(38\) 的位数最小的数之一(\(8\ 16\ 32\ 34\ 38\))而非 \(18\) ,尽管 \(18\) 也能到 \(38\): \(18\ 26\ 32\ 34\ 38\)

下面我们根据一个数 \(x\) 的末尾的数是几来进行分类

既然题目没有问到我们最少次数,我们就统一规定把结尾弄成以 \(2\) 结尾.(当然这是数字中所有的数的结尾均非 \(5\)\(0\) 的情况,含有 \(5\)\(0\) 情况待会再说 ,那种情况很好搞 )

下面所指的进位指的是数字末尾变化到 \(2\) 时,十位数字要增大几

对于末尾是 \(1\) 的数 ,他末尾的数的变化历程是 \(1\ 2\ 4\ 8\ 6\ ...\) 变化成结尾是\(2\)的话进 \(0\)
对于末尾是 \(2\) 的数 ,他末尾的数的变化历程是 \(2\ 4\ 8\ 6\ 2\ ...\) 变化成结尾是\(2\)的话进 \(0\)
对于末尾是 \(3\) 的数 ,他末尾的数的变化历程是 \(3\ 6\ 2\ 4\ 8\ 6\ ...\) 变化成结尾是\(2\)的话进 \(1\)
对于末尾是 \(4\) 的数 ,他末尾的数的变化历程是 \(4\ 8\ 6\ 2\ 4\ 8\ 6\ ...\) 变化成结尾是\(2\)的话进 \(2\)
对于末尾是 \(5\) 的数 ,他末尾的数的变化历程是 \(5\ 0\ 0\ 0\ 0\ ...\) 变化成结尾是\(2\)的话进 \(INF\) 位.这种情况不在我们讨论范围之内~
对于末尾是 \(6\) 的数 ,他末尾的数的变化历程是 \(6\ 2\ 4\ 8\ 6\ ...\) 变化成结尾是\(2\)的话进 \(1\)
对于末尾是 \(7\) 的数 ,他末尾的数的变化历程是 \(7\ 4\ 2\ 4\ 8\ 6\ ...\) 变化成结尾是\(2\)的话进 \(1\)
对于末尾是 \(8\) 的数 ,他末尾的数的变化历程是 \(8\ 6\ 2\ 4\ 8\ 6\ ...\) 变化成结尾是\(2\)的话进 \(2\)
对于末尾是 \(9\) 的数 ,他末尾的数的变化历程是 \(9\ 8\ 6\ 2\ 4\ 8\ 6\ ...\) 变化成结尾是\(2\)的话进 \(2\)

对于变化到 \(2\) 需要十位进 \(2\) 的数,由于我们之前讲的,数字中包含 \(20\) 可以直接化简掉,因此我们无需进位
对于变化到 \(2\) 需要十位进 \(1\) 的数,我们可以把这个数减加10,以模拟变化到 \(2\) 过程的进位.(对于诸如 \(2\) 变化到 \(4\) ,\(4\) 变化到 \(8\) 这种不需要进位的过程可以忽略,只模拟需要进位的过程).然后再去掉包含的 \(20\) 的个数就可以了.(代码具体在下方 \(solve\) 函数)

操作完后,如果所有数的位数(是个十百千的那个位数)均为1或者2,有解.如果有一位数有两位数,无解.那么只需排序,判断一下 $a[1] $和 \(a[n]\) 即可

对于数组中含有 \(5\)\(0\) 的数,分为如下情况
① 如果数组中含有非 \(5\)\(0\) 结尾的数,一定无解
② 如果数组中所有数均为 \(5\)\(0\) .由于 \(5\) 操作一次后就变成 \(0\) 并且以后结果不在改变.如果数组中最大值与最小值的差超过了 \(5\) 那便肯定无解.并且如果是 \(10\ 15\) 这种以\(0\)结尾的数更小,以 \(5\) 结尾的数更大,也是无解的.

至此,所有情况讨论完毕,代码献上

代码

点击查看代码
#include<stdio.h>
#include<iostream>
#include<cstdlib>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=2e5+10;
int a[N];
void solve(int &x)
{
	switch(x%10)
	{
		case 1: x-=(x/20)*20;break;
		case 2: x-=(x/20)*20;break;
		case 3: x+=10;x-=(x/20)*20;break;
		case 4: x-=(x/20)*20;break;
		case 6: x+=10;x-=(x/20)*20;break;
		case 7: x+=10;x-=(x/20)*20;break;
		case 8: x-=(x/20)*20;break;
		case 9: x+=10;x-=(x/20)*20;break;
	}
}
int main()
{
	//freopen("uva.txt","r",stdin);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		int n;
		scanf("%d",&n);
		int f=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			if(a[i]%10==5||a[i]%10==0)f=1;
			else solve(a[i]);
			
		}
		sort(a+1,a+n+1);
		if(!f)
		{
			
			if(a[1]/10==0&&a[n]/10!=0)
			{
				printf("NO\n");
			}
			else printf("YES\n");
		}
		else
		{
			int f=1;
			for(int i=1;i<=n;i++)
			{
				if(a[i]%5!=0)
				{
					f=0;
					break;
				}
				if(i<n&&a[i]%10==0&&a[i+1]%10!=0)
				{
					f=0;
					break;
				}
			}
			if(a[n]-a[1]>5)f=0;
			if(f)printf("YES\n");
			else printf("NO\n");
		}	
	}
	
	return 0;
}

网上看到一个更为简单的做法..ε=(´ο`*)))唉.蒟蒻什么时候也能和大佬们一样优秀

末尾是 \(5\)\(0\) 的情况:
image

对于 非 \(5\)\(0\) 的情况:
具体方法是:对数组 \(a\) 排序,取出最大值.操作最大值,比如 \(17\) 操作结果:\(17\ 24 \ 28 \ 36 \ 42\)(最多只需要操作5次,后面就是循环了)
对最大值的每一步操作,都把他拿去和前面的 \(n-1\) 个数进行判断,如果 \(a_i\) 都能达到当前的最大值的这个操作结果,就说明可行.返回 \(true\) .如果这 \(5\) 个操作数都不行,说明真的就不行了.
当然不需要真的对于每一个 \(a_i\) 都按照题中的方法一步一步操作到 \(a_n\) ,只需对 \(a_n-a_1\)\(20\) ,剩下所需的步数如果是\(0\) \(\ 2\) \(\ 2+4\) \(\ 2+4+8\) 中的之一, 那便是可以到达的.

代码如下

点击查看代码
#include <stdio.h>
#include <algorithm>
typedef long long LL;
const LL N = (LL)2e5 + 5, rep[] = {0, 2, 4, 8, 6}, srep[] = {0, 2, 6, 14};
LL n, a[N];
inline bool check(LL x) {
    x %= 20;
    for (LL i = 0; i < 4; ++i)
        if (x == srep[i]) return true;
    return false;
}
int main(void) {
    LL t; for (scanf("%lld", &t); t--; ) {
        scanf("%lld", &n); bool flag = false;
        for (LL i = 1; i <= n; ++i) {
            scanf("%lld", &a[i]);
            if (a[i] % 5 == 0)  flag = true; //是否存在 x % 5 == 0 的数 x
        }
        if (flag) {
            bool ok = true;
            for (LL i = 1; i <= n; ++i) {
                if (a[i] % 10 == 5) a[i] += 5;
                if (i != 1 && a[i] != a[i - 1]) {ok = false; break;}
            }
            puts(ok ? "Yes" : "No");
            continue;
        }
        std:: sort(a + 1, a + 1 + n); //排序
        for (LL i = 1; i <= n; ++i) //转末位为2
            while (a[i] % 10 != 2) a[i] += a[i] % 10;
        for (LL i = 0; i < 5; ++i) { //最大数末位循环
            a[n] += rep[i]; flag = true;
            for (LL j = 1; j < n; ++j)
                if (!check(a[n] - a[j])) {flag = false; break;} //判断各数可行与否
            if (flag) break;
        }
        puts(flag ? "Yes" : "No");
    }
    return 0;
}
/*
转自https://www.cnblogs.com/dry-ice/p/cf1714e.html
大佬nb~
/*
posted @ 2022-09-17 21:09  LZH_03  阅读(48)  评论(0编辑  收藏  举报