(蓝桥杯C/C++)——基础算法(上)

目录

一、二分法

1.二分法简介

二分法简介-解题步骤

2.整数二分-简介

整数二分-模板

3.浮点二分-简介

浮点二分-模板

4.二分答案-简介

二分答案-模板​​​​​​​

二、位运算

1.位运算简介

2.常见的位运算

按位与AND(&)

[按位或OR( | )](#按位或OR( | ))

按位异或XOR(^)

按位取反(~)

按位左移(<<)

按位右移(>>)

3.位运算技巧

1.判断数字奇偶性

2.取二进制数的某一位

3.修改二进制中的某一位为1

4.快速判断一个数字是否为2的幂次方

5.获取二进制位中最低位的1

三、贪心算法

1.贪心算法介绍

2.贪心算法实现步骤

3.常见贪心模型和例题

四、模拟算法

1.模拟算法介绍

2.例题讲解

五、双指针

1.双指针介绍

2.对撞指针

3.快慢指针

六、构造

1.构造介绍

2.构造题目的特点

3.构造的应用场景

4.构造例题解析

七、​​​​​​​进制的转换

1.进制的本质

2.将任意进制转换为十进制

3.将十进制转换为任意进制


一、二分法

1.二分法简介

二分法是一种高效的查找方法,它通过将问题的搜索范围一分为二(两边具有明显的区别),迭代地缩小搜索范围,直到找到目标或确定目标不存在。

分法适用于有序数据集合 ,并且每次迭代可以将搜索范围缩小一半。

分法本质上也是枚举,但和暴力枚举不同,二分法利用数据结构的单调性减少了很多不必要的枚举,从而极大地提高了效率,一般可以将O(n)的枚举优化到O(logn)。

常见的二分类型有:

(1)整数二分

(2)浮点二分

(3)二分答案(最常见)

二分法简介-解题步骤

1.研究并发现数据结构(或答案变量)的单调性

2.确定最大区间[L,R],确保分界点一定在里面,具体有一些细节:若以r作为答案,那么答案区间在[L+1,R],若以L作为答案,那么答案区问在[L,R-1]。

3.确定check函数,一般为传入mid(区间中某个下标),返回mid所属区域或返回一个值,当check函数较简单时可以直接判断。

4.计算中点mid =(L+R)/2,用check判断该移动L或R指针,具体移动哪个需要根据单调以及要求的答案来判断。

5.返回L或R,根据题意判断。

2.整数二分-简介

整数二分就是在一个有的有序数组上,进行二分查找,一般为找出某个值的位置)或者是找出分界点。

这个数组肯定是开的下的,其数组大小一般在1e6(1e6 = 1000000)以内。

区域划分如下图。

找第一个>=6的,即6的左边界,返回right

|----|----|--------|---------|----|----|----|
| 2 | 4 | 4 | 6 | 6 | 10 | 18 |
| a1 | a2 | a3 | a4 | a5 | a6 | a7 |
| | | ↑ left | ↑ right | | | |

找最后一个<=6的,即6的右边界,返回right

|----|----|----|----|--------|---------|----|
| 2 | 4 | 4 | 6 | 6 | 10 | 18 |
| a1 | a2 | a3 | a4 | a5 | a6 | a7 |
| | | | | ↑ left | ↑ right | |

整数二分-模板

找到升序数组a中的x第一次出现的位置

int L=0,R=1e9;

//注意这里的判断条件,这样可以保证L,L最终一定收敛到分界点代码如下(示例):

while(L + 1 != r) //L和R相邻时推出

{

int mid =(L + R) / 2;

//如果a为升序,说明mid偏大了,需要减小mid,就只能将r变小,即r = mid

if (a[mid] >= x)

R = mid //保持在所属区域

else L = mid;

}

cout << r << '\n';

3.浮点二分-简介

浮点二分不再是在有序数组上做二分查找,而是在某个实数范围内进行查找,因为实数域本身是单调的,所以也满足单调性,和整数二分的主要区别在于使用的变量类型 、退出的判断条件不同。

浮点二分-模板

eps是一个非常小的浮点数,用于处理浮点数比较时的误差或精度问题。

//计算单调函数f(x)的零点

double L = 0; R = 1e9, eps = 1e-6

//注意这里的判断条件,r最终一定收敛到分界点

while(R-1>= eps) //eps足一个极小量,设置为1e-6较合适

{

double mid = (1+R) / 2;

//f(x)单调递增,f (mid) >= 0,说明mid偏大了,需要减小mid,就只能将R变小,即r=mid

if (f (mid) >= 0)

R = mid;

else L = mid;

}

//最后返回L,R差别不大

cout << R << '\n';

4.二分答案-简介

二分答案是二分法中最常见也最重要的题型,考察的比较灵活,需要选手从题目中发现某个单调的函数,然后对其进行二分。

常见的模型是:
二分框架(时间复杂度O(logm)) + check兩数(时间复杂度O(n))

一般情况下,我们会将答案进行二分,然后在举出某个可能解后判断其是否可以更优或是否合法,从而不断逼近最优解。

二分答案的题的特征:如果已知某个答案,很容易判断其是否合法或更优。某些贪心问题可能可以转化成二分答案问题。

二分答案-模板

bool check(int mid)

{

bool res = true;

//do somtnin to check the authority of mid ...

return res;

}

int main()

{

int L = 0; R = 1e9;

while(1 + L !=R)

{

int mid = (L + R)/ 2;

//具体写法需要根据题意修改

if (check (mid)) L = mid;

else R= mid;

}

cout<< L <<'\n';//具体输出的内容需要根据题意判断

}

二、位运算

位运算是一种对二进制数的位进行操作的运算方式。

它直接对二进制数的每一位进行逻辑操作,而不考虑整个数的数值大小,一般情况下,位运算中每一位都相互独立,各自运算得出结果(左右移除外)在计算机科学和编程中,位运算常用于优化算法、位掩码操作、位字段处理等领域。

在竞赛中,位运算经常考察异或的性质、状态压缩、与位运算有关的特殊数据结构、构造题等。

注意:位运算只能应用于整数,且一般为非负整数,不能应用于字符、浮点等类型

1.位运算简介

在计算机中,整数是通过补码表示的,但是一般情况下,我们对负数进行位运算意义不大,99%情况下都是对正整数进行处理,而正数的原码=补码,所以我们直接考虑二进制数的原码,也就是直接地表示二进制数。

例如整数10,在计算机中存储如下(按照书写习惯,一般认为右边为低位,在左右移时尤为重要):

|-----|----|----|----|----|-----|---|---|---|---|
| val | 0 | 0 | 0 | 0 | ... | 1 | 0 | 1 | 0 |
| idx | 31 | 30 | 29 | 28 | ... | 3 | 2 | 1 | 0 |
| int 32位 ||||||||||

2.常见的位运算

按位与AND(&)

按位与运算符(&)用于对两个操作数的对应位进行逻辑与操作。它的运算规则是,只有当两个位都为1时,结果位才为1,否则为0。两个数字做与运算,结果不会变大。

同一为一,有零为零

|---|---|------|
| x | y | x&y |
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |

按位或OR( | )

按位或运算符(1)用于对两个操作数的对应位进行逻辑或操作。

它的运算规则是,只要两个位中有一个为1,结果位就为1,否则为0。

两个数字做或运算,结果不会变小。

同零为零,有一为一

|---|---|------|
| x | y | x|y |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |

按位异或XOR(^)

按位异或运算符(^)用于对两个操作数的对应位进行逻辑异或操作。

它的运算规则是,当两个位不同时,结果位为1,否则为0。

两个数字做异或运算,结果可能变大也可能变小,也可能不变。

|---|---|------|
| x | y | x^y |
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |

按位取反(~)

用于对操作数的每一位进行取反操作,即将0变为1,将1变为0。按位取反操作通常用于无符号整数(unsigned int/longlong),这是为了避免符号位取反造成千扰。

|---|-----|
| x | ~x |
| 0 | 1 |
| 1 | 0 |

按位左移(<<)

左移(<<)操作将一个数的二进制表示向左移动指定的位数。

移动后,低位补0,如果数据类型为有符号整型,注意移动的时候不要移动到符号位上,或者干脆使用无符号整型。1会移动到符号位上。

左移操作相当于对原数进行乘以2的幂次方的操作。

例如,对于整数5(二进制表示为00000101)执行左移3位操作,相当于执行5*(2^3)。

|---|---|---|---|---|---|---|---|
| 5 ||||||||
| 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |

|---|---|---|---|---|---|---|---|
| 5 << 3 ||||||||
| 0 | 0 | 1 | 0 | 1 | 0 | 0 | 0 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |

按位右移(>>)

左移(>>)操作将一个数的二进制表示向右移动指定的位数。

移动后,一般情况高位补0,如果数据类型为有符号整型,注意移动的时候让符号位为0,或者干脆使用无符号整型。如果符号位上有1不会被移走,这是负数位移的规则。

右移操作相当于对原数进行除以2的幂次方的操作例如,对于整数13(二进制表示为00001101)执行左移2位操作,相当于执行13/4向下取整,

|---|---|---|---|---|---|---|---|
| 13 ||||||||
| 0 | 0 | 0 | 0 | 1 | 1 | 0 | 1 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |

|---|---|---|---|---|---|---|---|
| 5 << 3 ||||||||
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |

3.位运算技巧

1.判断数字奇偶性

公式:x&1

如果结果为1说明是奇数,结果为0说明是偶数。

2.取二进制数的某一位

公式:x>>i&1

结果必然为0或1,表示x的二进制表示中的第i位。

3.修改二进制中的某一位为1

公式:x|(1<<i)

将x的第i位或上1,则x[]变为1,其他位上或上0没有影响。

修改为0就类似地用与运算即可,就是构造出只有第i位为0,其他位都为1的一个二进制数然后和x做与运算。

4.快速判断一个数字是否为2的幂次方

公式:x &(x-1)

如果x为2的幂次方,则x的二进制表示中只有一个1,x-1就有很多个连续的1并且和x的1没有交集,两者与运算一定为0,可以证明其他情况必然不为0。

5.获取二进制位中最低位的1

公式:lowbit(x)=x&-x

如果x=(010010),则lowbit(x)=(000010)。

常用于数据结构树状数组中。

三、贪心算法

1.贪心算法介绍

学习目标:

1.理解贪心算法的基本概念和原理。

2.掌握贪心算法的基本思想和解题思路。

3.理解常见贪心模型,并能够分析并解决简单的贪心问题。

贪心的基本原理:每一步都选择局部最优解,而尽量不考虑对后续的影响,最终达到全局最优解。

贪心的局限性:贪心算法不能保证获得全局最优解,但在某些问题上具有高效性。

贪心的特征:贪心选择性质、最优子结构性质(根据我的观察,很多贪心的题目会出现"不同的操作产生的贡献相同"的特征,在此特征下我们每次选择代价最小的)。

贪心的类型多且杂,难以划分,需要不断练习和积累。

2.贪心算法实现步骤

1.确定问题的最优子结构(贪心往往和排序、优先队列等一起出现)。

2.构建贪心选择的策略,可能通过"分类讨论"、"最小代价"、"最大价值"等方式来思考贪心策略。简单验证贪心的正确性,采用句式一般是:这样做一定不会使得结果变差、不存在比当前方案更好的方案等等。

3.通过贪心选择逐步求解问题,直到得到最终解。

3.常见贪心模型和例题

最近同学们表现出色,他们的老师决定给他们分发糖果,肖思购买了 几个不同种类的糖果,用小写的阿拉怕字母表示,每个糖果须分发给一个同学,并且每个同学至少要分到一个糖果,同学们的开心程度定义为他们所分到的糖果组成的字特串 s[i] 的字典序,恩希望同学们的开心程度相差尺量小,因此他要找到一种方客,使得所有糖果组成的字特串中字曲序最大的字符电尽可能小。请输出能够实现字典序最小可能的max(s|1|, s(2], s[3]....s|z])

#Include <bits/stac++.h>

using namespace std;

const int N = 1e6 +9;

char s[N];

int = main()

{

int n, x; cin >> n >> x;

cin > s+1;

sort (s+1,s+1+n);

if(s[1] == s[n])

{

for(int i = 1; i <= n / x + (n % x ? 1 :0); ++i)

cout << s[1];

}

else if(s[1] == s[n])

{

for(int i = x ; i <= m ; ++ i)

cout << s[i];

}

else

cout << s[x];

return 0;

}

四、模拟算法

1.模拟算法介绍

模拟算法通过模拟实际情况来解决问题,一般容易理解但是实现起来比较复杂,有很多需要注意的细节,或者是一些所谓很"麻烦"的东西。模拟题一般不涉及太难的算法,一般就是由较多的简单但是不好处理的部分组成的,考察选手的细心程度和整体的逻辑思维。一般为了使得模拟题写的逻辑清晰一些,经常会写比较多的小函数来帮助解题,例如int和string的相互转换、回文串的判断、日期的转换、各种特殊条件的判断等等。

2.例题讲解

2020 年春节期间,有一个特殊的日期引起了大家的注意:2020年2月2日。因为如果将这个日期按yyyymmdd" 的格式写成一个8位数是 20200202,恰好是一个回文数。我们称这样的日期是回文日期,

有人表示 20200202 是"千年一遇"的特殊日子。对此小明很不认同,因为不到2年之后就是下一个回文日期:20211202即2021年12月2日。

也有人表示 20200202 并不仅仅是一个回文日期,还是一个 ABABBABA型的回文日期。对此小明也不认同,因为大约 100 年后就能遇到下一个ABABBABA型的回文日期:21211212即 2121年12月 12日。算不上"千年遇",顶多算"千年两遇"

给定一个8位数的日期,请你计算该日期之后下一个回文日期和下一个 ABABBABA 型的回文日期各是哪一天。

#include <iostream>

using namespace std;

bool isLeap(int y){

return (y%4==0&&y%100!=0)||(y%400==0);

}

bool check(int year,int month,int day){//判断是否为合法日期

if(month>12||month==0) return false;

if(day>31) return false;

if(month==2){

if(isLeap(year)&&day>29)

return false;

if(!isLeap(year)&&day>28)

return false;

}

if(month==4||month==6||month==9||month==11){

if(day>30) return false;

}

return true;

}

int main()

{

int n,i;

cin>>n;

int a,b,c,d,e,f,g,h;//8位数字

int year,month,day;

bool flag=false;

for(i=n+1;i<=99999999;i++){

year=i/10000;

month=(i%10000)/100;

day=i%100;

a=i%10;

b=(i/10)%10;

c=(i/100)%10;

d=(i/1000)%10;

e=(i/10000)%10;

f=(i/100000)%10;

g=(i/1000000)%10;

h=(i/10000000)%10;

if(a==h&&b==g&&c==f&&d==e&&flag==false){

if(check(year,month,day)){

cout<<i<<endl;

flag=true;//只输出一个回文

}

}

if(a==h&&b==g&&c==f&&d==e&&a==c&&b==d){

if(check(year,month,day)){

cout<<i<<endl;

break;

}

}

}

return 0;

五、双指针

1.双指针介绍

双指针算法是一种常用的算法技巧,它通常用于在数组或字符串中进行快速查找、匹配、排序或移动操作。

双指针并非真的用指针实现,一般用两个变量来表示下标(在后面都用指针来表示)。双指针算法使用两个指针在数据结构上进行选代,并根据问题的要求移动这些指针。双指针往往也和单调性、排序联系在一起,在数组的区间问题上,暴力法的时间复杂度往往是O(n^2)的,但双指针利用"单调性"可以优化到O(n)。

常见的双指针模型有:
(1)对撞指针
(2)快慢指针

2.对撞指针

指的是两个指针 left、right(简写为1,r)分别指向序列第一个元素和最后一个元素。然后1指针不断递增,r不断递减,直到两个指针的值相撞或错开(即1>=r),或者满足其他要求的特殊条件为止。

对撞指针一般用来解决有序数组或者字符串问题(常见于区间问题):

查找有序数组中满足某些约束条件的一组元素问题:比如二分查找、数字之和等问题

字符串反转问题:反转字符串、回文数、颠倒二进制等问题。

对撞指针------求解步骤

1.使用两个指针 left,right。left 指向序列第一个元素,即:left=1,right 指向序列最后-个元素,即:right=n。

2.在循环体中将左右指针相向移动,当满足一定条件时,将左指针右移,left++。当满足另外一定条件时,将右指针左移,right--

3.直到两指针相撞(即left==right),或者满足其他要求的特殊条件时,跳出循环

|----|----|----|----|----|----|----|
| a1 | a2 | a3 | a4 | a5 | a6 | a7 |
| left↑→ ←↑right |||||||

对撞指针例题

题目描述

给定一个长度为 n的字符串 S、请你判断字符串 S是否回文

回文判定

对撞指针,每次判断s[1]和s[r]是否相等,如果相等就都移动一步,否则直接判断为"不是回文串"

#include <iostream>

using namespeace std;

const int N = 1e6 + 9;

char s[N];

int main()

{

ios::sync_with_stdio(o),cin.tie(o),cout.tie(o);

cin >> s + 1;

int n = strlen(s + 1);

int l = 11, r = n;

bool ans = trun;

while(l < r && ans)

{

if (s[l] != s[r] ans = false;

l ++, r++;

}

cout << (ans ? "Y" : "N") <<'\n';

return 0;

}

3.快慢指针

快慢指针一般比对撞指针更难想,也更难写,

指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢,

移动快的指针被称为快指针,移动慢的指针被称为慢指针。

为了方便理解,我们成快指针为r,慢指针为1,这样慢指针和快指针构成区间[1,]。

两个指针以不同速度、不同策略移动,直到快抬针移动到数组尾端,或者两指针相交,满足其他特殊条件时为止。

快慢指针------求解步骤

1.使用两个指针 l、r。l一般指向序列第一个元素,即: l=1,r一般指向序列第零个元素,即:r=0。即初始时区间[l,r」=[1,0]表示为空区间。

2.在循环体中将左右指针向右移动。当满足一定条件时,将慢指针右移,即1++。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即r++,保持1,r为合法区间3.到指针移动到数组尾端(即 l==n且r==n),或者两指针相交,或者满足其他特殊条件时跳出循环体。

|--------|-------|----|----|----|----|----|----|
| | a1 | a2 | a3 | a4 | a5 | a6 | a7 |
| right↑ | left↑ | | | | | | |
| → ||||||||

|----|--------|----|----|---------|----|----|
| a1 | a2 | a3 | a4 | a5 | a6 | a7 |
| | left ↑ | | | right ↑ | | |

对撞指针例题

题目描述

给定一个长度为 n 的序列 a1,42,......,a„ 和一个幫数 S。

对于一个连续区间如果它的区间和大于或等于S,则称它为美丽的9区间,

对于一个美图的区间,如果其区间长度越短,它就越美丽,

请你从序列中找出最美丽的区间。

利用快慢指针,保持区间[i,j]尽可能小且和>=S。

在这里当[i,j]的sum>=S后,再增加j没有意义,所以i,j都不动了,当i增大后sum可能<S,此时i不可能左移,所以只能右移,于是i,j都只能右移,出现左移。

#include <iostream>

using namespeace std;

const int N = 1e5 + 9;

int a[N],S;

int main()

{

ios::sync_with_stdio(o),cin.tie(o),cout.tie(o);

int n, S;

cin >> nn >>S;

for(int i = 1; i <= n; ++i)

cin >> a[i];

int ans = n + i;

for(int i = 1,j = 0, sum = 0; i <= n; ++i)

{

//考虑移动j,即j++

while(i > j || (j + 1 <= n &&sum < S))

sum += a[++j];

if(sum >= S)

ans = min(ans, j - i + 1);

sum - a[i];

}

cout << (ans > n ? 0 : ans)<<'\n';

return 0;

}

六、构造

1.构造介绍

构造题在比赛和解决问题的过程中确实是常见的一类题型。它们通常要求解题者通过观察问题的结构和规律,找到一种通用的方法或模式,使得在问题规模增大时,依然能够高效地得到答案。

在解决构造题时,以下几点思考是很重要的:

观察问题规模的增长:了解问题随着规模的增大,答案的变化趋势。这可以帮助

你找到一种通用的解决方案。

推广规律:尝试将你观察到的规律推广到更大的问题规模上。这可能涉及到数学

归纳法或者其他类似的思考方式。

考虑状态转移 (适用于动态规划等问题):如果问题可以通过状态转移来求解,那么要仔细考虑从一个状态到另一个状态的转移会带来什么影响。
模式识别 :尝试寻找问题中的模式或者特征,这有助于你更好地理解问题的本质。
**实践和练习:**通过解决大量的构造题,你会逐渐培养出发现规律和应用通用方法的能力。注意特殊情况:一些构造题在特定的情况下可能会有不同的解法或者规律,要注意考虑这些特殊情况。

总的来说,解决构造题需要一定的观察力、归纳能力和数学思维。随着练习的增多,你会变得越来越熟练在这类问题上。​​​​​​​

2.构造题目的特点

构造题的显著特点之一是其高自由度:

也就是说一道题的构造方式可能存在多种,但往往会有一种相对简单且能够满足题意的构造方式。这种特性看似降低了难度,使问题更易理解,但实际上,这种高自由度往往会导致考生在面对题目时感到迷茫,难以找到明确的解题思路。

面对构造题的特点,我在做题时一般通过以下方法帮助我更有效地解决这类问题:
分析题目要求和条件:

·仔细阅读题目,确保你理解了题目的要求和所给的条件。

·弄清楚问题的背景和目标,明确你需要构造的内容或解决的问题。
尝试特例和极端情况:

试图找出一些特殊情况下的解法,这可以帮助你更好地理解问题的本质

。探索极端情况,看看在极端条件下是否会出现特殊的构造方式或者规律。
寻找模式和规律:

·观察题目中是否存在一些明显的模式或者规律,这可能是解决问题的关键。

·尝试从已知的情况中找到一般性的解法,并推广到更一般的情况。

尝试逆向构造:

·从问题的反面思考也可以给出有用的线索。尝试反向推导出符合条件的情况。
使用数学归纳法:

·尝试使用数学归纳法证明某种构造方式在所有情况下都成立。
灵活运用已知知识:

将已学的数学、物理、逻辑等知识灵活应用,可能会为你找到新的解法。
反复实践和总结:

多做类似的题目,总结解题经验,找出有效的解题方法,虽然不存在通解,但是部分构造题可能会用到相似的套路,如果能够有做题的广度并且经常总结,那么对于你解决构造题目是非常有帮

助的。

保持耐心和信心:

构造题可能需要时间和多次尝试才能找到合适的解法,保持耐心和信心是很重要的

那什么是构造

其实从我前面的描述中,大家也能感受到,构造题目很难找到一个准确的形式化定义,也没有一个通用的解题方法。因此,接下来我们将分享一些经典例题,以帮助大家初步了解构造题的类型。虽然我们不能涵盖所有可能的模式和类型,但可以选择几个比较常见的套路,让大家熟悉一下。

3.构造的应用场景

构造题目在算法竞赛中起着重要的作用,它们旨在考察选手对问题的抽象能力、发现规律的能力以及解决问题的创造性思维。以下是一些构造题目在算法竞赛中的常见应用场景:

·数学问题:

·构造满足一定条件的数列、集合或排列组合。

·利用数学关系构造出特定的解。

图论问题:

·构造特定的图结构,如树、图、有向图等。

·根据题意构造出满足条件的图。

字符串处理:

·构造满足字符串性质的解,如回文串、循环字符串等。

·构造满足特定字符串操作的解。

组合与排列:

构造出满足一定条件的排列或组合。

根据条件构造特定的排列组合解。

游戏策略:

设计游戏规则,构造出有趣的游戏场景,考验选手的策略思维。

逻辑推理:

构造逻辑谜题或者推理题,要求选手根据题目信息进行推理,得出正确答案。

数据结构:

构造特定的数据结构,如堆、树、图等,要求选手在构造的基础上进行一系列操作。

动态规划:

构造状态转移方程,设计合适的状态表示,构造动态规划解,

贪心算法:

构造出合适的贪心策略,使得贪心策略能够得到最优解。

模拟问题:

设计模拟题,要求选手模拟特定场景或过程,根据模拟的结果得出答案。

这些是一些常见的构造题目应用场景。构造题目的特点是多样化,可以涵盖许多不同的领域和难度级别,有助于培养选手的创造性解决问题的能力。因此,在算法竞赛中,构造题目通常被认为是一种重要的题型。

4.构造例题解析

【题目描述】数学老师给小明出了一道等差数列求和的题目。但是粗心的小明忘记了一部分的数列,只记得其中N个整数。现在给出这N个整数,小明想知道包含这N个整数的最短的等差数列有几项,请输出这几项。

【输入描述】输入的第一行包含一个整数N。 第二行包含N个整数A,,A,···,A.。(注意A,~A,,并不一定是按等差数列中的顺序给出)(对于所有评测用例,2≤N<100800,0≤Ai≤10°。)

【输出描述】输出一个序列表示答案

所有数字间距离最小的间隔是公差吗?

等差数列的最小间隔(实际上不是公差),例如(2,5,7},最小的间隔是2,但公差不是2,是1。

这其实一个GCD构造问题,可以通过计算给定数字间的所有间隔的最大公约数(GCD)来确定。

把n个数据排序,计算它们的间隔,对所有间隔做GCD,结果为公差。

同时,通过最小值、最大值和公差,可以计算出等差数列的最少数量。

最少数量等于=(最大值-最小值)/公差+1。

从最小值到最大值依次输出即可。

七、​​​​​​​进制的转换

1.进制的本质

对于一个十进制数字,比如说153,其本质是每一个数位上的数字乘上这一位上的权重,

即:

153=(1x102)+(5 x10)+(3 x10°)

而二进制,只不过是把10换成了2,任意一个非负整数都有唯一的一个二进制表示:

(153)10 =

在计算机中,数字均通过二进制补码表示,所以学习进制转换尤为重要。

2.将任意进制转换为十进制

假设给了一个数组来表示一个k进制(假设k>10)的整数,我们该如何得到它的十进制数?

|-----|------|------|------|------|------|
| | k^4 | k^3 | k^2 | k^1 | k^0 |
| a | 1 | 3 | 10 | 5 | 7 |
| idx | 1 | 2 | 3 | 4 | 5 |

1l x= 0;

for(int i = 1;i <= n; ++ i)

x=x* k + a[i];

cout << x<< '\n';

一般来说,这个k进制的数组可以通过对输入字符串的处理得到。

3.将十进制转换为任意进制

假设现在有一个十进制数x,如何转换为k进制呢?

我们可以先假设一个x的k进制表达式,再逐步地去求解。

x = x +...+ x + x

对于这个表达式,我们可以快速计算出a0=x%k

计算出a0之后怎么办,我们只需要将x/=k,就可以将原本的a1放到a0位置上,再同样解即可。

x/k = x +...+ x

11 x;

cin >>x;

while(x) a[++ cnt]=x % k,x /= k;

reverse(a+1,a+1+cnt);//注奖翻转一下,才能使得高位在1的位置

例如十进制的11转换为二进制,根据这个规则得到的a数组为[1,1,0,1],而实际上11的二进制为[1,0,1,1]。

相关推荐
怀澈12221 分钟前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++
chnming19871 小时前
STL关联式容器之set
开发语言·c++
带多刺的玫瑰1 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
爱敲代码的憨仔1 小时前
《线性代数的本质》
线性代数·算法·决策树
威桑1 小时前
MinGW 与 MSVC 的区别与联系及相关特性分析
c++·mingw·msvc
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
yigan_Eins1 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法
Mr.131 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++
阿史大杯茶1 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
陌小呆^O^1 小时前
Cmakelist.txt之win-c-udp-server
c语言·开发语言·udp