问题来源:codeforces Polycarp and Div 3 1005D
http://codeforces.com/problemset/problem/1005/D
英文原文请见链接,于下只做简述
本题可以这样理解:将所给的由数字组成的字符串分割成数段(不限),欲求出所分割的部分中,最多能有几个部分能被3整除(这里能被3整除指的是mod 3=0的数,也就是说,0也包含在内)
例如原题所给出的
2 0 1 9 2 0 1 8 1
可被分割成的其中一组(最优割法)是
2 | 0 | 1 | 9 | 201 | 81
这样的6份。其中 0,9,201,81符合要求,也就是说,以这串数列作为输入,最终输出的答案是4.
由题目可知,数列的数位最多可到2*10^5,远远超出了int或者long long的范围。也就是说,普通的n%3==0判断方法是绝对行不通的,但是判断3的倍数的方法大家肯定都知道,于此不多做赘述,而由此种方法分割出的最大值9*2*10^5,int类型完全没问题。
那么由此思路继续向下想呢?以贪心的策略大致可以得到这样一种算法:
(1)遇见mod3==0的个位数便分割
(2)遇到3的倍数且其中任何一部分都无法被3整除,
例:123 是3的倍数 但是其中包含能被3整除的部分12|3 所以最终需经过判断使其变为12,3
而132这类 经过此种贪心策略则会被分切为1|3|2,由于无论哪一种最后结果都是1,所以也并不会出现问题。
(3)如果从此位开始,向后无论延伸几位,都无法组成符合要求的结果,那么便将此位单独切下。例如112由第一位“1”为头,无法分出任何结果,于是便将其分成1|12,其后部分再由上述规则继续分割。
于下是本人的程序,但是这种方法并不是此篇的重点,因此也不加标注。另,此贪心依然存在漏洞,请求高人点拨。
#include<bits/stdc++.h>
using namespace std;
int a[2000001];
int main()
{
char s;
int m=0;
s=getchar();
while(isdigit(s))
{
m++;
a[m]=s-48;
s=getchar();
}
int ans=0;
int now=1;
while(now<=m)
{
while(a[now]%3==0&&now<=m)
{
now++;
ans++;
}
int now22=now;
int ans2=0;
while(now<=m)
{
ans2=ans2+a[now];
ans2%=3;
now++;
if(ans2%3==0)
{
ans++;
break;
}
if(now>m)
{
now=now22+1;
break;
}
if(a[now]%3==0&&now<=m)
{
break;
}
}
}
cout<<ans;
}
结束贪心,接下来就是这次要详细分析的内容了(但是篇幅可能差不多)
首先:引入这种方法的关键,依然是对能被3整除的数,这一点开始的。
首先我们需要得到一条结论:若 两数mod3 结果相同,那么两数相减必然能被3整除。
由于结论极其简单,在此便不多证明了。
那么在这里就可以将程序的时间复杂度缩短为O(m)了(一次遍历)。
既然宏观简单,那么这此就直接切入细节
下为程序:
#include<bits/stdc++.h>
using namespace std;
int num[200001];
int mod[3];
int m=0;
int main()
{
char c;
c=getchar();
while(isdigit(c))
{
m++;
num[m]=c-48;
c=getchar();
}
int sum=0;
int ans=0;
mod[0]=1;
for(int i=1;i<=m;i++)
{
sum+=num[i];
sum%=3;
if(mod[sum]!=0)
{
ans++;
sum=0;
mod[1]=0;
mod[2]=0;
}
else
{
mod[sum]=i;
}
}
cout<<ans;
return 0;
}
那么如下就来分段分析了
char c;
c=getchar();
while(isdigit(c))
{
m++;
num[m]=c-48;
c=getchar();
}
首先是输入部分,将输入的字符转化为num数组保存的,每一位都是个位数的数列,并记录下了长度m。
int sum=0;
int ans=0;
mod[0]=1;
接下来是3个定义,sum用来累加判断,ans顾名思义,而mod则是重点,也正是因为mod数组所以才需要将这三句单独列出。其用意也是为了特别指出,mod[0]所赋的值一直保持为1,不会改变,用意将于下阐述。
for(int i=1;i<=m;i++)
{
//————————————————————————————————————————————————————————
sum+=num[i];
sum%=3; //计算余数
//————————————————————————————————————————————————————————
if(mod[sum]!=0)
{
ans++; //当余数1或2出现第二次,表示其中出现结果
sum=0; //另外,因为mod[0]值一直保持为1,故能直
mod[1]=0; //接被3整除的值可以直接被判至此程序块
mod[2]=0; //另外注意还原目前状态
}
//————————————————————————————————————————————————————————
else
{
mod[sum]=1;
}
}
为了方便查阅,再次汇总一下各变量用途
m:原数列长度
sum:用来累加求和,计算除3余数
mod[3]:标记余数状态
num数组:原数列
详情请见注释
以上便是总结的全部内容,本篇的目的是互相的学习与交流,如有疏忽欢迎指出。
另,此文定位难度偏低,各位大神见笑了。