枚举算法(acw)

枚举算法

枚举算法是我们在日常中使用到的最多的一个算法,它的核心思想就是:枚举所有的可能。

枚举法的本质就是从所有候选答案中去搜索正确的解,使用该算法需要满足两个条件:

        (1)可预先确定候选答案的数量;

        (2)候选答案的范围在求解之前必须有一个确定的集合。

01 - 连号区间数(蓝桥杯真题)

题目链接:1210. 连号区间数

题目详情:

小明这些天一直在思考这样一个奇怪而有趣的问题:

在 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 - 递增三元组

题目链接:1236. 递增三元组

题目详情:

给定三个整数数组

A=[A1,A2,…AN]
B=[B1,B2,…BN]
C=[C1,C2,…CN]

请你统计有多少个三元组 (i,j,k)(i,j,k) 满足:

  1. 1≤i,j,k≤N
  2. 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 - 特别数的和

题目链接 - 1245.特别数的和

题目详情:

小明对数位中含有 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值