题解 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)\)的公式:
于是,这道题就完美的解决啦!
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\),重要的事情说三遍!!!