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\) 的情况:
对于 非 \(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~
/*