题解 GDFZOJ 2020普转提十连测day4

零、写在前面

看原比赛戳这儿

本次比赛题目难度:普及(不超过第三题)

这次比赛主要目的是考查对数据结构的掌握程度

一、T1 序列

看原题戳这儿

1、审题

给出一个长度为\(n\)的整数序列 \(,\)要求删掉一个连续子串后序列中没有相同元素 \(,\) 请问至少要删掉多长的子串\(?\)

乍一看很多人肯定会说,这不是一道水题吗?直接把所有相同的去掉不就行了吗?

这就代表你没有认真地审题。

再读一次题:给出一个长度为\(n\)的整数序列 \(,\)要求删掉一个连续子串后序列中没有相同元素 \(,\) 请问至少要删掉多长的子串\(?\)

所以肯定不能直接去掉啦。

再看一看数据范围:\(1\le n\le1000,0\le a_i \le10^9\)

\(emm······\)看来桶也不能用了呢,那应该怎么办呢,事实上这道题需要用到\(map\)\(Hash\)

2、概念

已经会了\(Hash\)\(map\)的同学请跳过本部分\(······\)

Map

推荐文章:C++ map用法总结(整理)C++中的STL中map用法详解

Hash

推荐文章:C++:哈希C++ STL中哈希表 hash_map从头到尾详细介绍

3、做题

这道题有三个难点:起点是啥,终点是啥,怎么判断是否满足条件?

起点是啥? 对于这个问题,我们只需要枚举区间起点,再看在起点之前有没有重复的元素,如果有重复的就直接退出循环

终点是啥 刚才我们枚举了起点,但如果没有重复的呢,这时我们要枚举终点

怎么判断是否满足条件 当我们求出了起点和终点之后,我们就可以看最短能有多短(终点取最前值)了

时间复杂度\(O(n)\)

4、代码

#include<bits/stdc++.h>
using namespace std;
int a,b,c,d,ans=1e9;
int aa[1000001];
int main()
{
	scanf("%d",&a);
	for(int i=1;i<=a;++i) scanf("%d",aa+i);
	for(int i=1;i<=a;++i)
	{
		int tmp=a-i+1;
		map<int,int> hash;
		for(int j=1;j<i;++j)
		{
			if(hash[aa[j]])
			{
				printf("%d",ans);
				return 0;
			}
			hash[aa[j]]++;
		}
		for(int j=a;!hash[aa[j]]&&j>=1;j--) hash[aa[j]]++,tmp--;
		ans=min(ans,tmp);
	}
	printf("0");
	return 0;
}

二、T2 数字

看原题戳这儿

1、审题

给出两个整数 \(n,m\),求\(1\)\(n\)的所有整数中,能被\(m\)整除的整数的个位数字之和。输入包含多组数据。

数据范围:\(1\le m,n\le10^{16},T \le 100\)

又是一道题意简单的题,不过\(n\)的范围太过于硕大,所以必须思考\(O(1)\)的算法

2、规律

经过一番的思(da)考(biao),我们发现对于\(\forall m\)都有:\(m\)的倍数的个位数字都是循环的,而且循环节(不保证最小)都是10。

找到了以上的规律之后,我们就可以开始愉快的做题了

3、做题

首先我们可以暴力枚出循环节是什么(反正也就10个),开一个数组\(sum\)记录循环节,$ sum_i\(表示循环节中的第\)i\(个的个位是多少,然后将这些全部都加起来为\)c$,表示一个循环节的个位的总和是多少。

然后我们就可以判断区间\([1,n]\)之间有多少个循环节,这个很容易求出,就是\(n/m\),然后再乘上一个循环节的个位的总和,就可以求出来了。

可是这就完了吗?

并不是,我们算一算现在我们已经求了几个数,发现目前只求出了\(n / m * m\)个,我们知道\(c++\)\(n / (10 * m) * (10 * m)\)并不一定等于\(n\),所以一定还有遗漏的,那是多少呢,当然就是\(n-n/(10 * m) * (10 * m)\)啦,因为现在剩下的这些数的数量已经小于\(m\)了,所以我们用\((n-n/(10 * m) * (10 * m))/m\)就可以算出来还剩多少个没算啦。

综合以上内容我们完美地推出了一个\(O(1)\)的公式:

\[ans=c * (a/(10 * b))+\sum\limits_{i=1}^{(n-n/(10 * m) * (10 * m))/m}sum_i \]

于是,这道题就完美的解决啦!

4、代码

#include<bits/stdc++.h>
using namespace std;
long long int a,b,c,d,ans;
long long int aa[1000001];
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld%lld",&a,&b);c=d=ans=0;
		for(int i=1;i<=10;++i) aa[i]=b*i%10;
//		for(int i=1;i<=10;++i) printf("%d ",aa[i]);printf("\n");
		for(int i=1;i<=10;++i) c+=aa[i];
		ans=(c*(a/(10*b))),a=a-a/(10*b)*(10*b);
//		printf("%lld %lld %lld %lld ",a,10*b,a/(10*b),c);
		d=a/b;
		for(int i=1;i<=d;++i) ans+=aa[i];
		printf("%lld\n",ans);
	}
	return 0;
}

T3 有趣的数

看原题戳这儿

1、审题

将题目简化一下之后就是:对于一个正整数,对于其所有的数位,当且仅当它恰好有一位与其他位不同,则称这个数为有趣的数,求\([L,R]\)中有多少个有趣的数。

数据范围:\(100\le L\le R\le 10^{16}\)

首先我们看到了对于有趣的数的定义,发现判断一个数是不是有趣的数并不难,但是这道题的\(n\)又是过于硕大,所以必须考虑更快的方法

2、做题

因为数据过大,所以直接暴力枚举每一个数再判断是肯定不行的,那我们枚举什么呢?

答案是枚举有趣的数。

我们需要开四层循环,第一层枚举位数,第二层枚举其他位,第三层枚举那个特殊的一位,最后一层枚举特殊的那一位在哪儿,这样的话我们就可以求出一个有趣的数啦,然后我们只需要再判断一下就可以了,时间复杂度\(O(17 * 10 * 10 * 17 * 17)\),十分之小

3、代码

#include<bits/stdc++.h>
using namespace std;
long long int a,b,c,d,ans;
bool ck(long long int l,long long int ding,long long int bian,long long int wei)
{
	long long int sum=0;
	if(bian==0&&wei==1) return false;
	if(ding==0&&wei!=1) return false;
	for(long long int i=1;i<=l;++i)
	{
		sum*=10;
		if(i==wei) sum+=bian;
		else sum+=ding;
	}
	if(a<=sum&&sum<=b) return true;
	else return false;
}
int main()
{
	scanf("%lld %lld",&a,&b);
	for(long long int i=3;i<=17;++i)
	for(long long int j=0;j<=9;++j)
	for(long long int k=0;k<=9;++k)
	{
		if(j==k) continue;
		for(long long int l=1;l<=i;++l)
		if(ck(i,j,k,l)) ans++;
	}
	printf("%lld\n",ans);
	return 0;
}

T4 欢乐ABC

看原题戳这儿

1、审题

\(PiPi\)随手在纸上写下了一个字符串,然后他突然想到了一个问题,在这个字符串中,有多少个非空连续子串,其中\(A\)字符的数量,\(B\)字符的数量和\(C\)字符的数量三个量相等。

数据范围:\(|S|\le10^6\)

乍一看到这个数据,我们就知道只能做\(O(n)\)的算法了

2、做法

首先,我们需要对\(A B C\)三个字母都求一次前缀和,分别命名为A、B、C

经过一番的思考,我们发现若S是一个合法的非空子串,起始点和终止点为\(i\)\(j\),必须满足$ A_{i-1}-B_{i-1}=A_j-B_j\(且\) B_{i-1}-C_{i-1}=B_j-C_j\(,于是我们就对于\)\forall i\(,都将\)(A_i-B_iB_i-C_i)$看成一个二元组,然后
用map求出有多少对相同的二元组,最后记入答案就行啦

代码

#include<bits/stdc++.h>
using namespace std;
int a,b,c,d,ans;
int aa[100001],bb[100001];
char s[100001];
int main()
{
	cin>>s;a=strlen(s);
	for(int i=0;i<a;++i) aa[i+1]=s[i]-'A'+1;
//	for(int i=1;i<=a;++i) printf("%d ",aa[i]);printf("%d\n",a);
	for(int i=3;i<=a;i+=3)
	{
		memset(bb,0,sizeof(bb));
		for(int j=1;j<=i;++j) bb[aa[j]]++;
		for(int j=i+1;j<=a;++j)
		{
			bb[aa[j]]++;
			bb[aa[j-i]]--;
//			printf("%d %d %d %d %d\n",i,j,bb[1],bb[2],bb[3]);
			if(bb[1]==bb[2]&&bb[3]==bb[2]) ans++;
		}
	}
	printf("%d\n",ans);
	return 0;
}
/*
ABACABACB
*/

五、写在后面

这次题目总体不难,但是一定要记得开\(long long\),一定要记得开\(long long\),一定要记得开\(long long\),重要的事情说三遍!!!

\[\text{十年}OI\text{一场空,不开}long long \text{见祖宗} \]

完美撒花!!!

posted @ 2020-07-30 16:38  zhnzh  阅读(308)  评论(1编辑  收藏  举报