- 枚举算法是我们在日常中使用到的最多的一个算法,它的核心思想就是:枚举所有的可能。
- 枚举法的本质就是从所有候选答案中去搜索正确的解,使用该算法需要满足两个条件:
(1)可预先确定候选答案的数量;
(2)候选答案的范围在求解之前必须有一个确定的集合。- 枚举算法简单粗暴,他暴力的枚举所有可能,尽可能地尝试所有的方法。虽然枚举算法非常暴力,而且速度可能很慢,但确实我们最应该优先考虑的!因为枚举法变成实现最简单,并且得到的结果总是正确的。
- 枚举算法分为循环枚举、子集枚举、排列枚举三种。
2020 年春节期间,有一个特殊的日期引起了大家的注意:2020 年 2 月 2 日。因为如果将这个日期按 yyyymmdd
的格式写成一个 8 8 8 位数是 20200202
,恰好是一个回文数。我们称这样的日期是回文日期。
有人表示 20200202
是“千年一遇” 的特殊日子。对此小明很不认同,因为不到 2 年之后就是下一个回文日期:20211202
即 2021 年 12 月 2 日。
也有人表示 20200202
并不仅仅是一个回文日期,还是一个 ABABBABA
型的回文日期。对此小明也不认同,因为大约 100 100 100 年后就能遇到下一个 ABABBABA
型的回文日期:21211212
即 2121 年12 月12 日。算不上“千年一遇”,顶多算“千年两遇”。
给定一个 8 位数的日期,请你计算该日期之后下一个回文日期和下一个 ABABBABA
型的回文日期各是哪一天。
输入格式
输入包含一个八位整数 N N N,表示日期。
输出格式
输出两行,每行 1 1 1 个八位数。第一行表示下一个回文日期,第二行表示下
一个 ABABBABA
型的回文日期。
样例输入 #1
20200202
样例输出 #1
20211202
21211212
提示
对于所有评测用例, 10000101 ≤ N ≤ 92200229 10000101 \le N \le 92200229 10000101≤N≤92200229,保证 N N N 是一个合法日期的 8 8 8 位数表示。
蓝桥杯 2020 第二轮省赛 A 组 G 题(B 组 G 题)。
思路
题解
#include
using namespace std;
int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool check(int date){
int year = date/10000;
int month = (date/100)%100;
int day = date%100;
if(month < 0 || month > 12) return false;
if(day == 0 || month != 2 && day > days[month]) return false;
if(month==2){
int leap = year%4==0&&year%100!=0||year%400==0;
if(day>days[month]+leap) return false;
}
return true;
}
int main(){
int N;
int date1,date2;
cin>>N;
int year = N/10000;
for(int i=year;;++i){
date1=i;
int x=i;
for(int j=0;j<4;++j){
date1 = date1*10+x%10;
x/=10;
}
if(date1>N&&check(date1))
{
cout<<date1<<endl;
break;
}
}
int ab=N/1000000;
for(int i=ab;;++i){
int a=i/10;
int b=i%10;
int x=b*10+a;
date2 = i*1000000+ i*10000 +x*100 +x;
if(date2>N&&check(date2)) {
cout<<date2<<endl;
break;
}
}
}
对于一个字符串 S S S, 我们定义 S S S 的分值 f ( S ) f(S) f(S) 为 S S S 中恰好出现一次的字符个数。例如 f ( ′ ′ a b a ′ ′ ) = 1 f\left({ }^{\prime \prime} \mathrm{aba}{ }^{\prime \prime}\right)=1 f(′′aba′′)=1, f ( ′ ′ a b c ′ ′ ) = 3 f\left({ }^{\prime \prime} \mathrm{abc}{ }^{\prime \prime}\right)=3 f(′′abc′′)=3, f ( ′ ′ a a a a ′ ′ ) = 0 f\left({ }^{\prime \prime} \mathrm{aaa} \mathrm{a}^{\prime \prime}\right)=0 f(′′aaaa′′)=0 。
现在给定一个字符串 S [ 0.. n − 1 ] S[0 . . n-1] S[0..n−1](长度为 n n n),请你计算对于所有 S S S 的非空 子串 S [ i . . j ] ( 0 ≤ i ≤ j < n ) S[i . . j](0 \leq i \leq j
输入格式
输入一行包含一个由小写字母组成的字符串 S S S。
输出格式
输出一个整数表示答案。
样例输入 #1
ababc
样例输出 #1
21
提示
对于 20 % 20 \% 20% 的评测用例, 1 ≤ n ≤ 10 1 \leq n \leq 10 1≤n≤10;
对于 40 % 40 \% 40% 的评测用例, 1 ≤ n ≤ 100 1 \leq n \leq 100 1≤n≤100;
对于 50 % 50 \% 50% 的评测用例, 1 ≤ n ≤ 1000 1 \leq n \leq 1000 1≤n≤1000;
对于 60 % 60 \% 60% 的评测用例, 1 ≤ n ≤ 10000 1 \leq n \leq 10000 1≤n≤10000;
对于所有评测用例, 1 ≤ n ≤ 100000 1 \leq n \leq 100000 1≤n≤100000。
蓝桥杯 2020 第二轮省赛 A 组 H 题(B 组 H 题)。
思路
题解
#include
using namespace std;
const int N=1e5+5;
int pre[N],nex[N],a[27];
string s;
int main(){
cin>>s;
s="0"+s;
int n=s.length()-1;
for(int i=1;i<=n;i++)
{
int c=s[i]-'a';
pre[i]=a[c];
a[c]=i;
}
for(int i=0;i<26;i++)
a[i]=n+1;
for(int i=n;i;i--)
{
int c=s[i]-'a';
nex[i]=a[c];
a[c]=i;
}
long long int ans=0;
for(int i=1;i<=n;i++)
ans+=(long long)(i-pre[i])*(nex[i]-i);
cout<<ans;
}
众所周知,小葱同学擅长计算,尤其擅长计算一个数是否是另外一个数的倍数。但小葱只擅长两个数的情况,当有很多个数之后就会比较苦恼。现在小葱给了你 n n n 个数,希望你从这 n n n 个数中找到三个数,使得这三个数的和是 K K K 的倍数,且这个和最大。数据保证一定有解。
输入格式
从标准输入读入数据。
第一行包括 2 2 2 个正整数 n n n, K K K。
第二行 n n n 个正整数,代表给定的 n n n 个数。
输出格式
输出一行一个整数代表所求的和。
样例输入 #1
4 3
1 2 3 4
样例输出 #1
9
提示
【样例解释】
选择 2 2 2、 3 3 3、 4 4 4。
【数据约定】
对于 30 % 30\% 30% 的数据, n ≤ 100 n \le 100 n≤100。
对于 60 % 60\% 60% 的数据, n ≤ 1000 n \le 1000 n≤1000。
对于另外 20 % 20\% 20% 的数据, K ≤ 10 K \le 10 K≤10。
对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 5 1 \le n \le 10^5 1≤n≤105, 1 ≤ K ≤ 1 0 3 1 \le K \le 10^3 1≤K≤103,给定的 n n n 个数均不超过 1 0 8 10^8 108。
时限 1 秒, 256M。蓝桥杯 2018 年第九届省赛
思路
[蓝桥杯 2020 省 A1] 整数小拼接
有点像,所以也是用尺取法+排序,得到大数符合 k k k的倍数,优化掉小数。但是这道题需要3个数,所以我以为就是3重循环。时间复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn),应该是不能 A C AC AC的其实也就只用维护最大的3个数
题解
#include
using namespace std;
int a[100005];
stack<int>num[1005];
int n,k;
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
num[a[i]%k].push(a[i]);
}
int res=0;
for(int i=0;i<k;i++){
if(num[i].empty()){
continue;
}
for(int j=0;j<k;j++){
if(num[j].empty()){
continue;
}
int rm=(k-i-j+k)%k;
int tmp=0,ans1,ans2,ans3;
if(rm<j){
continue;
}
if(num[i].size()){
ans1=num[i].top();
tmp+=ans1;
num[i].pop();
if(num[j].size()){
ans2=num[j].top();
tmp+=ans2;
num[j].pop();
if(num[rm].size()){
ans3=num[rm].top();
tmp+=ans3;
num[rm].pop();
res=max(res,tmp);
num[rm].push(ans3);
}
num[j].push(ans2);
}
}
num[i].push(ans1);
}
}
cout<<res<<endl;
}
给定三个整数数组 A = [ A 1 , A 2 , ⋯ , A N ] A = [A_1, A_2,\cdots, A_N] A=[A1,A2,⋯,AN], B = [ B 1 , B 2 , ⋯ , B N ] B = [B_1, B_2,\cdots, B_N] B=[B1,B2,⋯,BN], C = [ C 1 , C 2 , ⋯ , C N ] C = [C_1, C_2,\cdots,C_N] C=[C1,C2,⋯,CN]。
请你统计有多少个三元组 ( i , j , k ) (i, j, k) (i,j,k) 满足:
输入格式
第一行包含一个整数 N N N。
第二行包含 N N N 个整数 A 1 , A 2 , ⋯ , A N A_1, A_2,\cdots, A_N A1,A2,⋯,AN。
第三行包含 N N N 个整数 B 1 , B 2 , ⋯ , B N B_1, B_2,\cdots, B_N B1,B2,⋯,BN。
第四行包含 N N N 个整数 C 1 , C 2 , ⋯ , C N C_1, C_2,\cdots, C_N C1,C2,⋯,CN。
输出格式
一个整数表示答案
样例输入 #1
3
1 1 1
2 2 2
3 3 3
样例输出 #1
27
提示
对于 30 % 30\% 30% 的数据, 1 ≤ N ≤ 100 1 \le N \le 100 1≤N≤100。
对于 60 % 60\% 60% 的数据, 1 ≤ N ≤ 1000 1 \le N \le 1000 1≤N≤1000。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 5 1 \le N \le 10^5 1≤N≤105, 0 ≤ A i , B i , C i ≤ 1 0 5 0 \le A_i, B_i, C_i \le 10^5 0≤Ai,Bi,Ci≤105。
思路
[蓝桥杯 2018 省 A] 倍数问题
有3个操作对象的时候,应该不要首先考虑尺取法(因为尺取法是对2个对象的)题解
#include
#define ll long long
using namespace std;
int a[100005];
int b[100005];
int c[100005];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
for(int i=0;i<n;i++)
{
scanf("%d",&b[i]);
}
for(int i=0;i<n;i++)
{
scanf("%d",&c[i]);
}
sort(a,a+n);
sort(b,b+n);
sort(c,c+n);
ll aa=0;//a数组标记
ll cc=0;//c数组标记
ll ans=0;
for(int i=0;i<n;i++)
{
// cout<<"----ai="<while (a[aa]<b[i]&&aa<n)//注意条件,不能等于
aa++;
while(c[cc]<=b[i]&&cc<n)//同上
cc++;
// cout<<"ai="<+= aa*(n-cc);
}
cout<<ans<<endl;
}
前缀和是指某序列的前n项和,可以把它理解为数学上的数列的前n项和,而差分可以看成前缀和的逆运算。合理的使用前缀和与差分,可以将某些复杂的问题简单化。
某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。
由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。
输入描述
第一行有两个整数:L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。
输出描述
包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。
示例一
输入
500 3
150 300
100 200
470 471
输出
298
备注
对于20%的数据,区域之间没有重合的部分;
对于其它的数据,区域之间有重合的情况。
题解
#include
using namespace std;
int main(){
int L,M,start,end,ans;
char a[10005];
scanf("%d%d",&L,&M);
memset(a, '1', L+1);
for(int i=0;i<M;++i){
scanf("%d%d",&start,&end);
memset(a+start, '0', end-start+1);
}
for(int i=0;i<strlen(a);++i){
if(a[i]=='1')++ans;
}
printf("%d",ans);
}
b[i]=a[i]+a[i+1]+…+a[n-2]+a[n-1];
b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j]
差分可以看成前缀和的逆运算。
差分数组
首先给定一个原数组a:a[1], a[2], a[3], a[n];
然后我们构造一个数组b : b[1] ,b[2] , b[3], b[i];
使得 a[i] = b[1] + b[2 ]+ b[3] +, + b[i]
也就是说,a数组是b数组的前缀和数组,反过来我们把b数组叫做a数组的差分数组。换句话说,每一个a[i]都是b数组中从头开始的一段区间和。
考虑如何构造差分b数组?
最为直接的方法:
如下:
a[0 ]= 0;
b[1] = a[1] - a[0];
b[2] = a[2] - a[1];
b[3] =a [3] - a[2];
b[n] = a[n] - a[n-1];
我们只要有b数组,通过前缀和运算,就可以在O(n) 的时间内得到a数组 。
基本思想是将数组按照中间元素分为两个部分,然后判断需要查找的元素在哪一部分中,这样每次查找就可以将待查找的范围缩小一半,直到找到目标元素或者确定目标元素不存在为止。
下面的图形是著名的杨辉三角形:
对于 20 % 20 \% 20% 的评测用例, 1 ≤ N ≤ 10 1 \leq N \leq 10 1≤N≤10;
对于所有评测用例, 1 ≤ N ≤ 1 0 9 1 \leq N \leq 10^9 1≤N≤109 。
蓝桥杯 2021 第一轮省赛 B 组 H 题。
思路
题解
#include
typedef long long ll;
using namespace std;
ll N;
ll C(int a, int b)//求第i行第j列的值
{
ll res = 1;
for (ll i = a, j = 1; j <= b; i--, j++)
{
res = res * i / j;
if (res > N)//如果中间结果超过N就直接返回
return res;
}
return res;
}
int main()
{
cin >> N;
for (int k = 16; k >= 0; k--)//一列一列的找
{
ll l = 2 * k, r = max(N, l), mid;
while (l <= r) {//对第k列二分查找行
mid = l + (r - l) / 2;//二分行
ll CC = C(mid, k);
if (CC == N)
break;
else if (CC > N)
r = mid - 1;
else
l = mid + 1;
}
if (C(mid, k) == N)
{//第mid行、第k列的数就是N
cout << (mid + 1) * mid / 2 + k + 1 << endl;
//杨辉三角形的行数符号公差为1的等差数列,故用等差数列求和公式
//加上第几列再加上1(因为列从0开始)即可得出该数的位置
break;
}
}
}
尺取法也叫双指针法
尺取法通常是对数组保存一对下标,即所选取的区间的左右端点,然后根据实际情况不断地推进区间左右端点以得出答案。尺取法比直接暴力枚举区间效率高很多,尤其是数据量大的时候,所以说尺取法是一种高效的枚举区间的方法,是一种技巧,一般用于求取有一定限制的区间个数或最短的区间等等。当然任何技巧都存在其不足的地方,有些情况下尺取法不可行,无法得出正确答案,所以要先判断是否可以使用尺取法再进行计算。
给定一个长度为 n n n 的数组 A 1 , A 2 , ⋯ , A n A_1,A_2,\cdots,A_n A1,A2,⋯,An。你可以从中选出两个数 A i A_i Ai 和 A j A_j Aj( i ≠ j i\neq j i=j),然后将 A i A_i Ai 和 A j A_j Aj 一前一后拼成一个新的整数。例如 12
和 345
可以拼成 12345
或 34512
。注意交换 A i A_i Ai 和 A j A_j Aj 的顺序总是被视为 2 2 2 种拼法,即便是 A i = A j A_i=A_j Ai=Aj 时。
请你计算有多少种拼法满足拼出的整数小于等于 K K K。
输入格式
第一行包含 2 2 2 个整数 n n n 和 K K K。
第二行包含 n n n 个整数 A 1 , A 2 , ⋯ , A n A_1,A_2,\cdots,A_n A1,A2,⋯,An。
输出格式
一个整数代表答案。
样例输入 #1
4 33
1 2 3 4
样例输出 #1
8
提示
对于 30 % 30\% 30% 的评测用例 1 ≤ n ≤ 1000 1\le n\le1000 1≤n≤1000, 1 ≤ k ≤ 1 0 8 1\le k\le10^8 1≤k≤108, 1 ≤ A i ≤ 1 0 4 1\le A_i\le10^4 1≤Ai≤104。
对于所有评测用例, 1 ≤ n ≤ 1 0 5 1\le n\le10^5 1≤n≤105, 1 ≤ k ≤ 1 0 10 1\le k\le10^{10} 1≤k≤1010, 1 ≤ A i ≤ 1 0 9 1\le A_i\le10^9 1≤Ai≤109。
蓝桥杯 2020 第一轮省赛 A 组 H 题。
思路
但想不到什么优化的方法
题解
#include
using namespace std;
const int N = 1e5 + 5;
typedef long long ll;
ll a[N];
string s[N], str;
ll n, k;
int cmp(string s1, string s2) {
if (s1.size() == s2.size()) {
if (s1 == s2) return 0;
else if (s1 < s2) return 1;
else return -1;
}
if (s1.size() < s2.size()) return 1;
else return -1;
}
void init() {
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
sort(a + 1, a + 1 + n);
for (int i = 1; i <= n; i ++ ) s[i] = to_string(a[i]);
str = to_string(k);
}
int main() {
init();
ll res = 0;
int l = 1, r = n;
while(l <= r) {
int t = cmp(s[l] + s[r], str);
if(t == 1) {
res += r - l;
l ++;
} else if(t == 0) {
res += r - l;
l ++, r --;
} else r --;
}
l = 1, r = n;
while(l <= r) {
int t = cmp(s[r] + s[l], str);
if(t == 1) {
res += r - l;
l ++;
} else if(t == 0) {
res += r - l;
l ++, r --;
} else r --;
}
cout << res;
}
小明维护着一个程序员论坛。现在他收集了一份“点赞”日志,日志共有 N N N 行。其中每一行的格式是 ts id
,表示在 t s ts ts 时刻编号 i d id id 的帖子收到一个“赞”。
现在小明想统计有哪些帖子曾经是“热帖”。如果一个帖子曾在任意一个长度为 D D D 的时间段内收到不少于 K K K 个赞,小明就认为这个帖子曾是“热帖”。
具体来说,如果存在某个时刻 T T T 满足该帖在 [ T , T + D ) [T,T+D) [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K K K 个赞,该帖就曾是“热帖”。
给定日志,请你帮助小明统计出所有曾是“热帖”的帖子编号。
输入格式
第一行包含三个整数 N N N、 D D D 和 K K K。
以下 N N N 行每行一条日志,包含两个整数 t s ts ts 和 i d id id。
输出格式
按从小到大的顺序输出热帖 i d id id。每个 i d id id 一行。
样例输入 #1
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
样例输出 #1
1
3
提示
对于 50 % 50\% 50% 的数据, 1 ≤ K ≤ N ≤ 1000 1 \le K \le N \le 1000 1≤K≤N≤1000。
对于 100 % 100\% 100% 的数据, 1 ≤ K ≤ N ≤ 1 0 5 1 \le K \le N \le 10^5 1≤K≤N≤105, 0 ≤ i d , t s ≤ 1 0 5 0 \le id, ts \le 10^5 0≤id,ts≤105。
时限 1 秒, 256M。蓝桥杯 2018 年第九届省赛
思路
题解
#include
using namespace std;
int t,n,k;
vector<int> q[100010];
int a[100010];
bool check(int x){
if(q[x].size()<k) return 0;
sort(q[x].begin(),q[x].end());
int l=0,r=0,res=0;
while(l<=r&&r<(int)q[x].size()){
if(q[x][r]-q[x][l]<n) {
r++;
res=r-l;
if(res>=k) return 1;
}
else l++;
}
return 0;
}
int main(){
cin>>t>>n>>k;
while(t--) {
int ts,id;
cin>>ts>>id;
q[id].push_back(ts);
}
int cnt=0;
for(int i=0;i<=1e5;i++) if(check(i)) cout<<i<<endl;
}
因为这种方法出现的题目较多
,然后在往其他学过的算法知识上靠,尽量别出现自己临场想的算法