Polycarp and Div 3

问题来源: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数组:原数列

详情请见注释

以上便是总结的全部内容,本篇的目的是互相的学习与交流,如有疏忽欢迎指出。

另,此文定位难度偏低,各位大神见笑了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值