枚举算法
枚举算法是我们在日常中使用到的最多的一个算法,它的核心思想就是:枚举所有的可能。
枚举法的本质就是从所有候选答案中去搜索正确的解,使用该算法需要满足两个条件:
(1)可预先确定候选答案的数量;
(2)候选答案的范围在求解之前必须有一个确定的集合。
01 - 连号区间数(蓝桥杯真题)
题目详情:
小明这些天一直在思考这样一个奇怪而有趣的问题:
在 1∼N 的某个排列中有多少个连号区间呢?
这里所说的连号区间的定义是:
如果区间 [L,R] 里的所有元素(即此排列的第 L 个到第 R 个元素)递增排序后能得到一个长度为 R−L+1的“连续”数列,则称这个区间连号区间。
当 N 很小的时候,小明可以很快地算出答案,但是当 N 变大的时候,问题就不是那么简单了,现在小明需要你的帮助。
输入格式
第一行是一个正整数 N,表示排列的规模。
第二行是 N 个不同的数字 Pi,表示这 N 个数字的某一排列。
输出格式
输出一个整数,表示不同连号区间的数目。
数据范围
1≤N≤10000
1≤Pi≤N输入样例1:
4 3 2 4 1输出样例1:
7输入样例2:
5 3 4 2 5 1输出样例2:
9样例解释
第一个用例中,有 7 个连号区间分别是:[1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4][1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4]
第二个用例中,有 9 个连号区间分别是:[1,1],[1,2],[1,3],[1,4],[1,5],[2,2],[3,3],[4,4],[5,5]
分析:
设一段区间 [ a , b ],Max 为区间内的最大值,Min为区间内的最小值,则有:
连号区间 < == > Max - Min == b - a
因为一段区间递增排序后,位置b为Max,位置a为Min,Max - Min == b - a。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
//1. 暴力做法
//时间复杂度:O(n^3logn)
//for(int i=0;i<n;i++) ;O(n)
// for(int j=i;j<n;j++) ;O(n)
// {
// sort(); ;O(nlogn)
// flag=true;
// for(int k=0;k<i-j+1;k++) ;O(n)
// {
// if() flag=false;
// }
// if(flag) res++;
// }
//优化为 O( n^2 )
const int N = 10010,INF = 100000;
int a[N];
int n;
int main()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
int res=0;
for(int i=0;i<n;i++) //枚举区间左端点
{
int maxv = -INF,minv = INF;
for(int j=i;j<n;j++) //枚举区间右端点
{
maxv=max(maxv,a[j]);
minv=min(minv,a[j]);
if((maxv - minv) == (j-i))
res++;
}
}
cout<<res;
return 0;
}
02 - 递增三元组
题目详情:
给定三个整数数组
A=[A1,A2,…AN]
B=[B1,B2,…BN]
C=[C1,C2,…CN]请你统计有多少个三元组 (i,j,k)(i,j,k) 满足:
- 1≤i,j,k≤N
- Ai<Bj<Ck
输入格式
第一行包含一个整数 N。
第二行包含 N 个整数 A1,A2,…AN。
第三行包含 N 个整数 B1,B2,…BN。
第四行包含 N 个整数 C1,C2,…CN。
输出格式
一个整数表示答案。
数据范围
1≤N≤10^5
0≤Ai,Bi,Ci≤10^5输入样例:
3 1 1 1 2 2 2 3 3 3输出样例:
27
分析:
100000数量级,应该想一个 nlogn 的算法
因为数组B和数组C的之间的取值互相限制,不是相互独立的,所以我们可以先枚举数组B里的数,此时数组A和数组C的取值完全由数组B决定,不会相互影响,我们只需要把数组A中满足条件的数量 乘以 数组B中满足条件的数量,即为我们的答案。
那么,枚举完 数组B 中的数后,我们如何快速的求出有多少个Ai、Ck满足要求呢?
对于每个 Bj :
1)在A中,有多少个 Ai 小于Bj
2)在C中,有多少个 Ck 大于Ai
解决办法 :
1) 前缀和
2)二分
3)双指针
Ai < Bj < Ck
方法1:前缀和
cnt[i]:表示在数组A中,i 这个数出现多少次
s[i] :表示数组A的前缀和
在A中,小于Bj的数的个数为 s[Bj - 1]
C数组同理
方法2:二分法
二分:找A数组的右端点
int l=0,r=n; while(l<r){ int mid = r+l+1>>1; if(a[mid]<b[j]) l=mid; else r=mid-1; }二分:找C数组的左端点
int l=0,r=n; while(l<r){ int mid = l+r >> 1; if(c[mid]>b[j]) r=mid; else l=mid+1; }
前缀和 - 代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 100010;
typedef long long LL;
int a[N],b[N],c[N];
int n;
int as[N]; //as[i]表示在A[]中有多少个数小于b[i]
int cs[N]; //cs[i]表示在C[]中有多少个数大于b[i]
int cnt[N],s[N];
//前缀和
//前缀和的时间复杂度为 O(数值范围)本题的数值范围和N相等,较小
//如果数值范围很大,需要用离散化代替前缀和
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]), a[i] ++ ;
for(int i=0;i<n;i++) scanf("%d",&b[i]), b[i] ++ ;
for(int i=0;i<n;i++) scanf("%d",&c[i]), c[i] ++ ;
//求as[]
for(int i = 0; i < n; i ++ ) cnt[a[i]]++;
for(int i = 1; i < N; i ++ ) s[i] = s[i - 1] + cnt[i]; //求cnt[]的前缀和
for(int i = 0; i < n; i ++ ) as[i] = s[b[i] - 1];
//求cs[]
memset(cnt, 0 , sizeof cnt);
memset(s, 0 ,sizeof s);
for(int i = 0; i < n; i ++ ) cnt[c[i]]++;
for(int i = 1; i < N; i ++ ) s[i] = s[i - 1] + cnt[i];
for(int i = 0; i < n; i ++ ) cs[i] = s[N-1] - s[b[i]];
//枚举每个b[i]
LL res = 0;
for(int i = 0; i < n; i ++ ) res += (LL)as[i] * cs[i];
cout<<res<<endl;
return 0;
}
二分法 - 代码
#include<cmath>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 100010;
typedef long long LL;
int a[N],b[N],c[N];
int n;
//二分法
int main()
{
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 res = 0;
for(int i = 0;i < n; i ++ )
{
int l = 0,r = n - 1;
//求as[]
while(l < r)
{
int mid = l + r + 1 >> 1;
if(a[mid] < b[i]) l = mid;
else r = mid - 1;
}
if(a[r] >= b[i]) r--;
int x = r+1;
cout<<"x:"<<x<<endl;
//求cs[]
l = 0,r = n - 1;
while(l < r)
{
int mid = l + r >> 1;
if(c[mid] > b[i]) r = mid;
else l = mid + 1;
}
//如果未找到大于b[i]的数,将数组下标标记为n,因为没有b[i]更大的数了
if(c[r] <= b[i]) r=n;
int y = n - r;
cout<<"y:"<<y<<endl;
res += x*y;
}
cout<<res<<endl;
return 0;
}
03 - 特别数的和
题目详情:
小明对数位中含有 2、0、1、9 的数字很感兴趣(不包括前导 0),在 1 到 40 中这样的数包括 1、2、9、10至 32、39 和 40,共 28 个,他们的和是 574。
请问,在 1 到 n 中,所有这样的数的和是多少?
输入格式
共一行,包含一个整数 n。
输出格式
共一行,包含一个整数,表示满足条件的数的和。
数据范围
1≤n≤10000
输入样例:
40输出样例:
574
扩展
求数的每个位数
while(x) { int t = x % 10; x /= 10; }字符串数字转化为int型
"2019" ==> 2019 string str = "2019"; int x = 0; for(int i = 0; i < str.length(); i ++) { x = x*10 + str[i] - '0'; }
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 10000;
int main()
{
int n;
cin>>n;
int res = 0;
for(int i = 1; i <= n; i++)
{
int x = i;
while(x)
{
int t = x % 10;
x /= 10;
if(t == 2 || t == 0 || t == 1 || t == 9)
{
res += i;
break;
}
}
}
cout<<res<<endl;
return 0;
}

527

被折叠的 条评论
为什么被折叠?



