蛮力算法(循环模拟)—数组的简单应用

89

_2.1  蛮力法概述_

2.1.1 蛮力算法定义

蛮力算法(brute fore method)也称为穷举法(或枚举法)或暴力法,它是算法设计中最常用的方法之一。蛮力法的基本思想是对问题的所有可能状态一一测试,直到找到解或将全部可能状态测试为止。

蛮力法是一种简单直接地解决问题的方法,通常直接基于问题的描述和所涉及的概念定义,找出所有可能的解。

然后选择其中的一种或多种解,若该解不可行则试探下一种可能的解。

2.1.2****使用蛮力法通常有如下几种情况:

  • 搜索所有的解空间:问题的解存在于规模不大的解空间中。
  • 搜索所有的路径:这类问题中不同的路径对应不同的解。
  • 直接计算:按照基于问题的描述和所涉及的概念定义,直接进行计算。往往是一些简单的题,不需要算法技巧的。
  • 模拟和仿真:按照求解问题的要求直接模拟或仿真即可。

2.1.3****直接采用蛮力法的一般格式

在直接采用蛮力法设计算法中,主要是使用循环语句和选择语句,循环语句用于穷举所有可能的情况,而选择语句判定当前的条件是否为所求的解。

基本格式:

for (循环变量x取所有可能的值) {   ┇   if (x满足指定的条件)      输出x;    ┇} 

_2.2数组占位_

   应用数组的值1表示有,0表示没有的特殊用法。

【例题2.1P13005门牌号问题

   宾馆里有一百个房间,从1-100编了号。第一个服务员把所有的房间门都打开了,第二个服务员把所有编号是2的倍数的房间“相反处理”,第三个服务员把所有编号是3的倍数的房间作“相反处理”…,以后每个服务员都是如此。当第100个服务员来过后,哪几扇门是打开的。(所谓“相反处理”是:原来开着的门关上,原来关上的门打开。)

【分析】此题较简单,用a[1],a[2],…,a[n]表示编号为1,2,3,…,n的门是否开着。模拟这些操作即可。用0表示关闭,1表示打开。每人循环对对应门牌号做操作   a[j]=!a[j];代码如下:

#include<cstdio> using namespace std; int main(){ int first=1; int a[100]={0};   for (int i=1;i<=100;++i)   for (int j=1;j<=100;++j)      if (j%i==0) a[j]=!a[j]; for (int i=1;i<=100;++i)      if (a[i]){        if(first) first=0;       else printf(” “);      printf(“%d”,i);       } printf(“\n”); return 0; }  程序执行步骤1.定义变量,用于首次打印不打空格2.定义数组,并初始化为0表示关3.表示100个服务员要进行操作4.表示有100房间门号5.第i个服务员对第j个门的操作6.循环打印开着的门7.如果门开着8.第一次打印不打空格 9.打印开着门的房间号 10.换行11.程序结束 

本题还有其它解法,读者可以通过数学推导,已检查计算结果的正确性。

【例题2.2P13006约瑟夫问题:

N个人围成一圈,从第一个人开始报数,数到M的人出圈;再由下一个人开始报数,数到M的人出圈;…输出依次出圈的人的编号。N,M由键盘输入。

    【分析】

     (1)由于对于每个人只有出圈和没有圈两种状态用0和1来表示。

     (2)开始的时候,给标志数组赋初值为0,即全部在圈内。

    (3)模拟报数游戏的过程,直到所有的人出圈为止。 

#include<iostream>using namespace std; int main(){     int n,m,s,f,t;     int a[1000]={0};      cin>>n>>m;                 f=0;  t=0;  s=0;                                     do {         ++t;           if (t==n+1) t=1;           if (a[t]==0) ++s;           if (s==m) {              s=0;              cout<<t<<” “;               a[t]=1;               f++;                }           } while(f!=n);          return 0; }   程序执行步骤1.定义变量2.根据题意开出数组大小3.输入n和m4.初始化变量 f出圈人数,t数组下标,s表示报数6.下标自增,注意此处避免了下标为07.数组下标的处理8.若未出圈,可报数9.报数等于m10.报数从0开始11.输出当前下标t12.出圈下标标志修改为113.出圈人数自增 14.循环判断15.程序结束 

   读者可以进一步思考,最后一个出圈人原有编号是多少?若果留k个人在圈内,他们原有的编号又是多少呢?

**【例题2.3】**0428_5 **蓝桥****杯体验赛—**质数方案数

【问题描述】

输入一个自然数n,求三个质数a,b,c,它们的和正好等于n, 即a+b+c=n;

求满足条件的方案数的个数。

【输入格式】 

一个正整数n 

【输出格式】

方案数个数,一个正整数

【样例输入】 

10

【样例输出】

【样例说明】

10=2+3+5   即10只能分解成2 3 5三个质数的和,10=5+3+2=2+3+5=…,只能算一种方案

【数据规模】

10<=n<1000000;

【算法思路】

  • 应用数组占位的思想求出1~1000000的所有质数,数组值为1表示质数,0表示合数。
  • 求三个质数的和等于n,设i<=j<=k,先循环i进行是否质数判断,然后循环j,可以求出k,在进行j和k是否为质数的判断,即可得到要求的方案数。
#include <iostream>#include <cstring>using namespace std; int a[1000001]={0,0,1}; int n,ans=0;int main(){    int i,j,k;    cin>>n;    memset(a,0,sizeof(a));    a[2]=1;    for(i=3;i<=1000000;i=i+2){     for(j=3;j*j<i;j++){     if(i%j==0)break; } if(j*j>i) a[i]=1;   } for(i=2;i<=n/3;i++){ if(a[i]==0) continue;  if(i==2) j=3;  else j=i+2; for(;j<n/2;j=j+2){ k=n-i-j;   if(k<j) continue;   if(a[j]+a[k]==2) ans++; } } cout<<ans<<endl;}

【例题2.4*】P20019修改数组(LQB2019)

 【问题描述】

给定一个长度为 N 的数组 A = [A1, A2, · · · AN],数组中有可能有重复出现 的整数。 现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改 A2, A3, · · · , AN。 当修改 Ai 时,小明会检查 Ai 是否在 A1 ∼ Ai−1 中出现过。如果出现过,则 小明会给 Ai 加上 1 ;如果新的 Ai 仍在之前出现过,小明会持续给 Ai 加 1 ,直 到 Ai 没有在 A1 ∼ Ai−1 中出现过。 当 AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。 现在给定初始的 A 数组,请你计算出最终的 A 数组。

【输入格式】

第一行包含一个整数 N。 第二行包含 N 个整数 A1, A2, · · · , AN 。

【输出格式】 输出 N 个整数,依次是最终的 A1, A2, · · · , AN。

【样例输入】

5

 2 1 1 3 4

【样例输出】 2 1 3 4 5

【评测用例规模与约定】 对于 80% 的评测用例,1 ≤ N ≤ 10000。 对于所有评测用例,1 ≤ N ≤ 100000,1 ≤ Ai ≤ 1000000。

【算法思路】

  • 应用数组b【1000000】进行是否占位的表示,其值为0 未被使用,1表示使用了。
  • A【】数组记录输入的数,c【】数组记录最终的结果
  • 将A【】数组第一个赋值给c【】数组的第一个,从A数组第二个开始,逐个遍历,从A数组的当前值,在b数组中是否被使用,若未使用进行填充,若使用了,则从b数组往后搜索,直到第一个未使用为止。
  • 依次循环操作下去即可。
#include <iostream>using namespace std;     int b[1000010]={0};int main(){     int n,i,j,k,ans=0;    int a[100005]={0};    int c[100005]={0};    cin>>n;    for(i=1;i<=n;i++){     cin>>k;     a[i]=k; } c[1]=a[1];b[a[1]]++; for(i=2;i<=n;i++){ j=a[i]; while(b[j]!=0){ j++; } c[i]=j; b[j]++; } for(i=1;i<=n;i++)    cout<<c[i]<<” “;}

_2.****3_ _数组用着预处理_

应用数组进行累加或累成进行处理。

例题2.5】P13004****数组—数组初始化

【问题描述】

长度为n的整型数组a[n],b[n]两个数组,已知a的n的元素已经被初始化,现需要对b进行初始化,要求b[i]=a[0]*a[1]*…*a[i-1]*a[i+1]*…*a[n-1](即从a[0]到a[n-1]不包含a[i]的乘积)请实现b的初始化,要求不使用除法,并且只能用一重循环。

【输入格式】

第一行输入正整数n:

第二行开始输入那个正整数,对数组a进行初始化,数与数之间用空格隔开

【输出格式】

输出对b进行初始化的结果:n个数,数与数之间用空格隔开

【样例输入】

2 1 1

【样例输出】

1 1

【说明】

【数据规模】

1<n<100000

【解题思路】

1、本题因数组a中可能存在0的情况不能应用除法进行计算。

2、用C数组实现a数组的冲前到后的累乘例如:c【i】=a[1]*a[2]*…*a[i-1]

3、用D数组实现a数组从后往前累乘,例如:d[i]=a[i+1]*a[i+2]*…*a[n]。

4、那么可以直接求的b数组的结果:例如b[i]=c[i]*d[i]

【参考代码】

#include <iostream>using namespace std; long long   a[100005]={0},b[100005]={0},c[100005]={0},d[100005]={0};int n;int main(){      int i,j,k;    cin>>n;    for(i=0;i<n;i++)         cin>>a[i];    c[0]=1;    for(i=1;i<n;i++)        c[i]=c[i-1]*a[i-1];    d[n-1]=1;    for(i=n-2;i>=0;i–)       d[i]=d[i+1]*a[i+1];    for(i=0;i<n;i++){     b[i]=c[i]*d[i];     cout<<b[i]<<” “; } }

例题2.6】P13013****数组— 数组价值

【问题描述】

给你一个长度为 n 的数组,编号为1~n ,现在我们从这个位置中选择一个位置k,则这个数组的价值定义为:一共有种n选择方案,现在想让你找到所有方案中这个价值最大是多少?

【输入格式】

输入第一行一个整数 ,代表数组的长度

接下来 n行,每一行一个整数 ai,代表数组元素的每一个数

【输出格式】

对于每一组测试数据,输出一个答案代表要求的最大价值

【样例输入】

5

2

1

4

3

5

【样例输出】

168

【说明】

数据规模:

1≤n≤1000000    1≤ai≤100

【解题思路】

1、根据题意,先用另一数组s【】进行累积求和即:s[i]=a[1]+a[2]+…+a[i]

2、再来一次循环,用sum来求取前i项的平方和,即,sum=sum+a[i]*a[i];

3、问题的解可以理解为sum*(s[n]-s[i])的所有情况求取最大值了;

【参考代码】

#include <iostream>using namespace std; int a[1000001]; int s[1000001]; int n;int main(){   int i,j; long long sum=0,max=0;  cin>>n;  for(i=1;i<=n;i++ ){  cin>>a[i];  s[i]=s[i-1]+a[i];  }  for(i=1;i<=n;i++){   sum=sum+a[i]*a[i];   j=sum*(s[n]-s[i]);   if(j>max)max=j;  }  cout<<max<<endl;  return 0;}

例题2.7】P13015****数组—足够数组

【问题描述】

你收到一个长度为N的正整数序列 A=a1,a2,…,an  和一个整数K,请问A中有多少个连续子序列满足下面这个条件:连续子序列中所有数字之和至少为K。不论子序列中内容是否相同,只要子序列是从A的不同位置开始的,我们都把它视为不同的子序列。

【输入格式】

输入两行,第一行两个整数,第二行N个数字表示这个序列。

N,K

a1,a2,…,an

【输出格式】

输出一个整数,满足条件的连续子序列个数。(提示:结果可能很大)

【样例输入1】

4 10

6 1 2 7

【样例输出1】

2

【样例输入2】

3 5

3 3 3

【样例输出2】

3

【样例输入3】

10 53462

103 35322 232 342 21099 90000 18843 9010 35221 19352

【样例输出3】

36

【说明】

数据规模:

1≤ai≤10^5

1≤N≤10^5

1≤K≤10^10

【解题思路】

1、该题先仍然用数组s进行元素的累积求和,即s[i]=s[i-1]+a[i]às[i]=a[1]+a[2]+…+a[i];

2、应用双指针从前往后进行搜索,若st作为左边指针,i为右边的指针,先st=0不动,i从1开始在s数组往后搜索,若满足s[i]-s[st]>=k;那么从0到st之间的所有数再到i位置都满足,所以这时的方案数为ans=ans+st;

3、然后i不动,st(左指针)从前往后搜索,寻找st到i之间有多少方案满足提议,若搜索到s[i]-s[st]<k;这时st不动,i往继续搜索,…,

4、重复上述的操作,虽然是两个循环,实际上是把a数组的每个数最多搜索了2次,总的循环次数O(2*n)

【参考代码】

#include <iostream>using namespace std;long long a[100005];long long s[100005];int main(){   long long n,k,i,j,st,ed,ans=0; cin>>n>>k; for(i=1;i<=n;i++){ cin>>a[i]; s[i]=s[i-1]+a[i]; } st=0;ed=0;    for(i=1;i<=n;i++){     if(s[i]-s[st]<k && st==0) continue;     ans=ans+st;     for( ;st<=i;st++){     if(s[i]-s[st]>=k) ans++;     else      break; } } cout<<ans<<endl;  return 0;}

_2.****4****ACM集训题库习题_

**【**练习1 】P13014****数组—三分数组

【问题描述】

给出一个有n 个整数的数组a[1],a[2],…,a[n],有多少种方法把数组分成3 个连续的子序列,使得各子序列的元素之和相等。也就是说,有多少个下标对i,j (2≤i≤j≤n-1),满足:sum(a[1]..a[i-1]) = sum(a[i]..a[j]) = sum(a[j+1]..a[n])

【输入格式】

第1 行:1 个整数n

接下来n 行,每行1 个整数,表示a[i]

【输出格式】

第1 行:1 个整数,表示答案,如果不能3 等分,输出0

【样例输入1】

5

1 2 3 0 3

【样例输出1】

2

【样例输入2】 

4

0 1 -1 0

【样例输出2】

1

【样例输入3】  

2

4 1

【样例输出3】

0

【说明】

数据规模:

(1 <= n <= 5*10^5)

(|a[i]| <= 10^9)

【解题思路】

1、本题是求将一个序列平均分成三段,先累积求和,看最终求得的结果能被3整除,则可以分成三份,否则不能分成三份。设每份的平均值为ans。

2、然后冲前往后搜索累积求和的数组,分别统计为ans,2*ans以及3*ans的个数,然后应用乘法原理,将这三个数相乘,就是我们所要得到的结果。

3、若平均值为0,要单独处理,只需统计为0时的个数,然后应用组合进行求解。

【参考代码】

#include <iostream>using namespace std;int a[500001];long long s[500005];int main(){   int n,i,j,k,f,sum=0,cou=1; cin>>n; for(i=1;i<=n;i++){ cin>>a[i]; s[i]=s[i-1]+a[i]; } int ans=0; if(s[n]%3==0) ans= s[n]/3; else  cou=0; if(n<3)cou=0; j=0,k=0,f=0;    for(i=1,j=0;i<=n;i++){     if(s[i]==ans) j++;     if(s[i]==2*ans)k++;     if(s[i]==3*ans)f++; } if(cou==1){ if(ans==0) cou=(f-1)*(f-2)/2; else cou=j*k*f;     } cout<<cou<<endl;  return 0;} 

**【**练习2 】P13016****数组— 全零的数组

【问题描述】

约翰有个整数数组。约翰每次给数组中所有非零数都加上一个相等的数,可以加负数。

约翰希望尽快让数组变得全是0。约翰想知道最少要多少次操作。

【输入格式】

输入格式

第一行一个整数n。

第二行n个整数a1,a2,…,an。

【输出格式】

输出格式

输出一个整数,约翰最少要操作多少次。

【样例输入1】

5

1 1 1 1 1

【样例输出1】

1

【样例输入2】

3

2  0 -1

【样例输出2】

2

【样例输入3】

4

5 -6 -5 1

【样例输出3】

4

【说明】

数据规模:

1≤n≤10^5

1≤|ai|≤10^5

【解题思路】

1、从提议可以理解到,只有相同的数,加同样的数才可能为0,有多少个不同的数,就要做做多少次操作,本身为0的除外。

2、问题转化为,统计n个数字中,不同数字的个数问题。

3、由于数字输入有正负,而数组没有负数下表,可根据规模,将数组设成a[200010],将输入的数都统一加上100000,这样就不会有负数下表的情况了。

4、应用数字下表来表示输入的数,里面的值表示输入同样数字的个数。

5、最后统计数组不为0的个数。若输入数字存在0的情况要减去。

【参考代码】

#include <iostream>using namespace std;int a[200010]={0};int main(){     int n,i,k,x,ans=0;   cin>>n;   for(i=1;i<=n;i++){      cin>>x;      a[x+100000]++;   }   for(i=0;i<=200000;i++){      if(a[i]!=0) ans++;   }   if(a[100000]!=0) ans–;   cout<<ans<<endl;  return 0;}

**【**练习3 】P13017****数组—求分数序列和

【问题描述】 

有一个分数序列 q1/p1,q2/p2,q3/p3,q4/p4,q5/p5,…. ,其中qi+1= qi+ pi, pi+1=qi, p1= 1, q1= 2。

比如这个序列前6项分别是2/1,3/2,5/3,8/5,13/8,21/13。求这个分数序列的前n项之和。

【输入格式】 

输入有一行,包含一个正整数n(n <= 1000)。

【输出格式】

输出有一行,包含一个浮点数,表示分数序列前n项的和,精确到小数点后4位。

【样例输入】

2

【样例输出】

3.5000

【说明】

数据规模:

1≤n≤30

【解题思路】

1、该题可应用题意,分别应用迭代求取分子和分母,并累积求和计算各项的累加,注意数据类型。

2、本题也可以声明两个数组,分别代表分子或分母,应用题目中表达式,循环递推计算得到,再来累积计算求和即可。

【参考代码】

#include <iostream>#include <cstdio>using namespace std; int main(){    int  n,i,j,k; float ans=0.0; float fz,fm,a; fz=2,fm=1; ans=fz/fm;     cin>>n; for(i=2;i<=n;i++){ a=fz; fz=a+fm; fm=a; ans=ans+fz/fm; } printf(“%.4f\n”,ans);}

**【**练习4 】P13019****数组— 全区间内的真素数

【问题描述】

找出正整数 M 和 N 之间(N 不小于 M)的所有真素数的个数。真素数的定义:如果一个正整数 P 为素数,且其反序也为素数,那么 P 就为真素数。例如,11,13 均为真素数,因为11的反序还是为11,13 的反序为 31 也为素数。

【输入格式】 

输入两个数 M 和 N,空格间隔

【输出格式】 

一个正整数,给定区间的真素数的个数

【样例输入】

10 35

【样例输出】

4

【说明】

样例说明:

在【10,35】之间的 11,13,17,31是真素数

数据规模:

1 <= M <= N <= 1000000。

【解题思路】

1、先用数组a将1~1000000之间的质数标志出来,注意代码技巧,很容易超时,经过下面代码的优化,标识1~1000000的质数循环的次数在3千多万次,不会超时。

2、然后按照题意从m到n之间进行搜索即可。(因搜索过程中,应用前面预先处理好的数组,不需要再对某个数进行是否质数的判断了)。

【参考代码】

#include <iostream>using namespace std;int a[1000100]={0,0,1,1};int main(){    int m,n,i,j,k,ans=0; m=1,n=1000000; for(i=3;i<1000000;i=i+2){ int f=0; for(j=3;j*j<=i;j=j+2)    if(i%j==0) {     f=1;break;    } if(f==0) a[i]=1; } for(i=m;i<=n;i++){ if(a[i]==0)continue; j=i,k=0; while(j){ k=k*10+j%10; j=j/10; } if(a[i]+a[k]==2)ans++; } cout<<ans;}

**【**练习5 】P13020****数组— 连续出现的字符

【问题描述】

给定一个字符串,在字符串中找到第一个连续出现至少k次的字符。

【输入格式】

第一行包含一个正整数k,表示至少需要连续出现的次数。1 <= k <= 1000。

第二行包含需要查找的字符串。字符串长度在1到1000之间,且不包含任何空白符。

【输出格式】

若存在连续出现至少k次的字符,输出该字符;否则输出No。

【样例输入】

3

abcccaaab

【样例输出】

c

【说明】

数据规模:

1 <= k <= 1000。

【解题思路】

1、本题实际上就是统计各个字母出现的次数,并输出第一次出现k次的字母。

2、应用数据类型转换,将字母转换

_2.1  蛮力法概述_

2.1.1 蛮力算法定义

蛮力算法(brute fore method)也称为穷举法(或枚举法)或暴力法,它是算法设计中最常用的方法之一。蛮力法的基本思想是对问题的所有可能状态一一测试,直到找到解或将全部可能状态测试为止。

蛮力法是一种简单直接地解决问题的方法,通常直接基于问题的描述和所涉及的概念定义,找出所有可能的解。

然后选择其中的一种或多种解,若该解不可行则试探下一种可能的解。

2.1.2****使用蛮力法通常有如下几种情况:

  • 搜索所有的解空间:问题的解存在于规模不大的解空间中。
  • 搜索所有的路径:这类问题中不同的路径对应不同的解。
  • 直接计算:按照基于问题的描述和所涉及的概念定义,直接进行计算。往往是一些简单的题,不需要算法技巧的。
  • 模拟和仿真:按照求解问题的要求直接模拟或仿真即可。

2.1.3****直接采用蛮力法的一般格式

在直接采用蛮力法设计算法中,主要是使用循环语句和选择语句,循环语句用于穷举所有可能的情况,而选择语句判定当前的条件是否为所求的解。

基本格式:

for (循环变量x取所有可能的值) {   ┇   if (x满足指定的条件)      输出x;    ┇} 

_2.2数组占位_

   应用数组的值1表示有,0表示没有的特殊用法。

【例题2.1P13005门牌号问题

   宾馆里有一百个房间,从1-100编了号。第一个服务员把所有的房间门都打开了,第二个服务员把所有编号是2的倍数的房间“相反处理”,第三个服务员把所有编号是3的倍数的房间作“相反处理”…,以后每个服务员都是如此。当第100个服务员来过后,哪几扇门是打开的。(所谓“相反处理”是:原来开着的门关上,原来关上的门打开。)

【分析】此题较简单,用a[1],a[2],…,a[n]表示编号为1,2,3,…,n的门是否开着。模拟这些操作即可。用0表示关闭,1表示打开。每人循环对对应门牌号做操作   a[j]=!a[j];代码如下:

#include<cstdio> using namespace std; int main(){ int first=1; int a[100]={0};   for (int i=1;i<=100;++i)   for (int j=1;j<=100;++j)      if (j%i==0) a[j]=!a[j]; for (int i=1;i<=100;++i)      if (a[i]){        if(first) first=0;       else printf(” “);      printf(“%d”,i);       } printf(“\n”); return 0; }  程序执行步骤1.定义变量,用于首次打印不打空格2.定义数组,并初始化为0表示关3.表示100个服务员要进行操作4.表示有100房间门号5.第i个服务员对第j个门的操作6.循环打印开着的门7.如果门开着8.第一次打印不打空格 9.打印开着门的房间号 10.换行11.程序结束 

本题还有其它解法,读者可以通过数学推导,已检查计算结果的正确性。

【例题2.2P13006约瑟夫问题:

N个人围成一圈,从第一个人开始报数,数到M的人出圈;再由下一个人开始报数,数到M的人出圈;…输出依次出圈的人的编号。N,M由键盘输入。

    【分析】

     (1)由于对于每个人只有出圈和没有圈两种状态用0和1来表示。

     (2)开始的时候,给标志数组赋初值为0,即全部在圈内。

    (3)模拟报数游戏的过程,直到所有的人出圈为止。 

#include<iostream>using namespace std; int main(){     int n,m,s,f,t;     int a[1000]={0};      cin>>n>>m;                 f=0;  t=0;  s=0;                                     do {         ++t;           if (t==n+1) t=1;           if (a[t]==0) ++s;           if (s==m) {              s=0;              cout<<t<<” “;               a[t]=1;               f++;                }           } while(f!=n);          return 0; }   程序执行步骤1.定义变量2.根据题意开出数组大小3.输入n和m4.初始化变量 f出圈人数,t数组下标,s表示报数6.下标自增,注意此处避免了下标为07.数组下标的处理8.若未出圈,可报数9.报数等于m10.报数从0开始11.输出当前下标t12.出圈下标标志修改为113.出圈人数自增 14.循环判断15.程序结束 

   读者可以进一步思考,最后一个出圈人原有编号是多少?若果留k个人在圈内,他们原有的编号又是多少呢?

**【例题2.3】**0428_5 **蓝桥****杯体验赛—**质数方案数

【问题描述】

输入一个自然数n,求三个质数a,b,c,它们的和正好等于n, 即a+b+c=n;

求满足条件的方案数的个数。

【输入格式】 

一个正整数n 

【输出格式】

方案数个数,一个正整数

【样例输入】 

10

【样例输出】

【样例说明】

10=2+3+5   即10只能分解成2 3 5三个质数的和,10=5+3+2=2+3+5=…,只能算一种方案

【数据规模】

10<=n<1000000;

【算法思路】

  • 应用数组占位的思想求出1~1000000的所有质数,数组值为1表示质数,0表示合数。
  • 求三个质数的和等于n,设i<=j<=k,先循环i进行是否质数判断,然后循环j,可以求出k,在进行j和k是否为质数的判断,即可得到要求的方案数。
#include <iostream>#include <cstring>using namespace std; int a[1000001]={0,0,1}; int n,ans=0;int main(){    int i,j,k;    cin>>n;    memset(a,0,sizeof(a));    a[2]=1;    for(i=3;i<=1000000;i=i+2){     for(j=3;j*j<i;j++){     if(i%j==0)break; } if(j*j>i) a[i]=1;   } for(i=2;i<=n/3;i++){ if(a[i]==0) continue;  if(i==2) j=3;  else j=i+2; for(;j<n/2;j=j+2){ k=n-i-j;   if(k<j) continue;   if(a[j]+a[k]==2) ans++; } } cout<<ans<<endl;}

【例题2.4*】P20019修改数组(LQB2019)

 【问题描述】

给定一个长度为 N 的数组 A = [A1, A2, · · · AN],数组中有可能有重复出现 的整数。 现在小明要按以下方法将其修改为没有重复整数的数组。小明会依次修改 A2, A3, · · · , AN。 当修改 Ai 时,小明会检查 Ai 是否在 A1 ∼ Ai−1 中出现过。如果出现过,则 小明会给 Ai 加上 1 ;如果新的 Ai 仍在之前出现过,小明会持续给 Ai 加 1 ,直 到 Ai 没有在 A1 ∼ Ai−1 中出现过。 当 AN 也经过上述修改之后,显然 A 数组中就没有重复的整数了。 现在给定初始的 A 数组,请你计算出最终的 A 数组。

【输入格式】

第一行包含一个整数 N。 第二行包含 N 个整数 A1, A2, · · · , AN 。

【输出格式】 输出 N 个整数,依次是最终的 A1, A2, · · · , AN。

【样例输入】

5

 2 1 1 3 4

【样例输出】 2 1 3 4 5

【评测用例规模与约定】 对于 80% 的评测用例,1 ≤ N ≤ 10000。 对于所有评测用例,1 ≤ N ≤ 100000,1 ≤ Ai ≤ 1000000。

【算法思路】

  • 应用数组b【1000000】进行是否占位的表示,其值为0 未被使用,1表示使用了。
  • A【】数组记录输入的数,c【】数组记录最终的结果
  • 将A【】数组第一个赋值给c【】数组的第一个,从A数组第二个开始,逐个遍历,从A数组的当前值,在b数组中是否被使用,若未使用进行填充,若使用了,则从b数组往后搜索,直到第一个未使用为止。
  • 依次循环操作下去即可。
#include <iostream>using namespace std;     int b[1000010]={0};int main(){     int n,i,j,k,ans=0;    int a[100005]={0};    int c[100005]={0};    cin>>n;    for(i=1;i<=n;i++){     cin>>k;     a[i]=k; } c[1]=a[1];b[a[1]]++; for(i=2;i<=n;i++){ j=a[i]; while(b[j]!=0){ j++; } c[i]=j; b[j]++; } for(i=1;i<=n;i++)    cout<<c[i]<<” “;}

_2.****3_ _数组用着预处理_

应用数组进行累加或累成进行处理。

例题2.5】P13004****数组—数组初始化

【问题描述】

长度为n的整型数组a[n],b[n]两个数组,已知a的n的元素已经被初始化,现需要对b进行初始化,要求b[i]=a[0]*a[1]*…*a[i-1]*a[i+1]*…*a[n-1](即从a[0]到a[n-1]不包含a[i]的乘积)请实现b的初始化,要求不使用除法,并且只能用一重循环。

【输入格式】

第一行输入正整数n:

第二行开始输入那个正整数,对数组a进行初始化,数与数之间用空格隔开

【输出格式】

输出对b进行初始化的结果:n个数,数与数之间用空格隔开

【样例输入】

2 1 1

【样例输出】

1 1

【说明】

【数据规模】

1<n<100000

【解题思路】

1、本题因数组a中可能存在0的情况不能应用除法进行计算。

2、用C数组实现a数组的冲前到后的累乘例如:c【i】=a[1]*a[2]*…*a[i-1]

3、用D数组实现a数组从后往前累乘,例如:d[i]=a[i+1]*a[i+2]*…*a[n]。

4、那么可以直接求的b数组的结果:例如b[i]=c[i]*d[i]

【参考代码】

#include <iostream>using namespace std; long long   a[100005]={0},b[100005]={0},c[100005]={0},d[100005]={0};int n;int main(){      int i,j,k;    cin>>n;    for(i=0;i<n;i++)         cin>>a[i];    c[0]=1;    for(i=1;i<n;i++)        c[i]=c[i-1]*a[i-1];    d[n-1]=1;    for(i=n-2;i>=0;i–)       d[i]=d[i+1]*a[i+1];    for(i=0;i<n;i++){     b[i]=c[i]*d[i];     cout<<b[i]<<” “; } }

例题2.6】P13013****数组— 数组价值

【问题描述】

给你一个长度为 n 的数组,编号为1~n ,现在我们从这个位置中选择一个位置k,则这个数组的价值定义为:!l一共有种n选择方案,现在想让你找到所有方案中这个价值最大是多少?

【输入格式】

输入第一行一个整数 ,代表数组的长度

接下来 n行,每一行一个整数 ai,代表数组元素的每一个数

【输出格式】

对于每一组测试数据,输出一个答案代表要求的最大价值

【样例输入】

5

2

1

4

3

5

【样例输出】

168

【说明】

数据规模:

1≤n≤1000000    1≤ai≤100

【解题思路】

1、根据题意,先用另一数组s【】进行累积求和即:s[i]=a[1]+a[2]+…+a[i]

2、再来一次循环,用sum来求取前i项的平方和,即,sum=sum+a[i]*a[i];

3、问题的解可以理解为sum*(s[n]-s[i])的所有情况求取最大值了;

【参考代码】

#include <iostream>using namespace std; int a[1000001]; int s[1000001]; int n;int main(){   int i,j; long long sum=0,max=0;  cin>>n;  for(i=1;i<=n;i++ ){  cin>>a[i];  s[i]=s[i-1]+a[i];  }  for(i=1;i<=n;i++){   sum=sum+a[i]*a[i];   j=sum*(s[n]-s[i]);   if(j>max)max=j;  }  cout<<max<<endl;  return 0;}

例题2.7】P13015****数组—足够数组

【问题描述】

你收到一个长度为N的正整数序列 A=a1,a2,…,an  和一个整数K,请问A中有多少个连续子序列满足下面这个条件:连续子序列中所有数字之和至少为K。不论子序列中内容是否相同,只要子序列是从A的不同位置开始的,我们都把它视为不同的子序列。

【输入格式】

输入两行,第一行两个整数,第二行N个数字表示这个序列。

N,K

a1,a2,…,an

【输出格式】

输出一个整数,满足条件的连续子序列个数。(提示:结果可能很大)

【样例输入1】

4 10

6 1 2 7

【样例输出1】

2

【样例输入2】

3 5

3 3 3

【样例输出2】

3

【样例输入3】

10 53462

103 35322 232 342 21099 90000 18843 9010 35221 19352

【样例输出3】

36

【说明】

数据规模:

1≤ai≤10^5

1≤N≤10^5

1≤K≤10^10

【解题思路】

1、该题先仍然用数组s进行元素的累积求和,即s[i]=s[i-1]+a[i]às[i]=a[1]+a[2]+…+a[i];

2、应用双指针从前往后进行搜索,若st作为左边指针,i为右边的指针,先st=0不动,i从1开始在s数组往后搜索,若满足s[i]-s[st]>=k;那么从0到st之间的所有数再到i位置都满足,所以这时的方案数为ans=ans+st;

3、然后i不动,st(左指针)从前往后搜索,寻找st到i之间有多少方案满足提议,若搜索到s[i]-s[st]<k;这时st不动,i往继续搜索,…,

4、重复上述的操作,虽然是两个循环,实际上是把a数组的每个数最多搜索了2次,总的循环次数O(2*n)

【参考代码】

#include <iostream>using namespace std;long long a[100005];long long s[100005];int main(){   long long n,k,i,j,st,ed,ans=0; cin>>n>>k; for(i=1;i<=n;i++){ cin>>a[i]; s[i]=s[i-1]+a[i]; } st=0;ed=0;    for(i=1;i<=n;i++){     if(s[i]-s[st]<k && st==0) continue;     ans=ans+st;     for( ;st<=i;st++){     if(s[i]-s[st]>=k) ans++;     else      break; } } cout<<ans<<endl;  return 0;}

_2.****4****ACM集训题库习题_

**【**练习1 】P13014****数组—三分数组

【问题描述】

给出一个有n 个整数的数组a[1],a[2],…,a[n],有多少种方法把数组分成3 个连续的子序列,使得各子序列的元素之和相等。也就是说,有多少个下标对i,j (2≤i≤j≤n-1),满足:sum(a[1]..a[i-1]) = sum(a[i]..a[j]) = sum(a[j+1]..a[n])

【输入格式】

第1 行:1 个整数n

接下来n 行,每行1 个整数,表示a[i]

【输出格式】

第1 行:1 个整数,表示答案,如果不能3 等分,输出0

【样例输入1】

5

1 2 3 0 3

【样例输出1】

2

【样例输入2】 

4

0 1 -1 0

【样例输出2】

1

【样例输入3】  

2

4 1

【样例输出3】

0

【说明】

数据规模:

(1 <= n <= 5*10^5)

(|a[i]| <= 10^9)

【解题思路】

1、本题是求将一个序列平均分成三段,先累积求和,看最终求得的结果能被3整除,则可以分成三份,否则不能分成三份。设每份的平均值为ans。

2、然后冲前往后搜索累积求和的数组,分别统计为ans,2*ans以及3*ans的个数,然后应用乘法原理,将这三个数相乘,就是我们所要得到的结果。

3、若平均值为0,要单独处理,只需统计为0时的个数,然后应用组合进行求解。

【参考代码】

#include <iostream>using namespace std;int a[500001];long long s[500005];int main(){   int n,i,j,k,f,sum=0,cou=1; cin>>n; for(i=1;i<=n;i++){ cin>>a[i]; s[i]=s[i-1]+a[i]; } int ans=0; if(s[n]%3==0) ans= s[n]/3; else  cou=0; if(n<3)cou=0; j=0,k=0,f=0;    for(i=1,j=0;i<=n;i++){     if(s[i]==ans) j++;     if(s[i]==2*ans)k++;     if(s[i]==3*ans)f++; } if(cou==1){ if(ans==0) cou=(f-1)*(f-2)/2; else cou=j*k*f;     } cout<<cou<<endl;  return 0;} 

**【**练习2 】P13016****数组— 全零的数组

【问题描述】

约翰有个整数数组。约翰每次给数组中所有非零数都加上一个相等的数,可以加负数。

约翰希望尽快让数组变得全是0。约翰想知道最少要多少次操作。

【输入格式】

输入格式

第一行一个整数n。

第二行n个整数a1,a2,…,an。

【输出格式】

输出格式

输出一个整数,约翰最少要操作多少次。

【样例输入1】

5

1 1 1 1 1

【样例输出1】

1

【样例输入2】

3

2  0 -1

【样例输出2】

2

【样例输入3】

4

5 -6 -5 1

【样例输出3】

4

【说明】

数据规模:

1≤n≤10^5

1≤|ai|≤10^5

【解题思路】

1、从提议可以理解到,只有相同的数,加同样的数才可能为0,有多少个不同的数,就要做做多少次操作,本身为0的除外。

2、问题转化为,统计n个数字中,不同数字的个数问题。

3、由于数字输入有正负,而数组没有负数下表,可根据规模,将数组设成a[200010],将输入的数都统一加上100000,这样就不会有负数下表的情况了。

4、应用数字下表来表示输入的数,里面的值表示输入同样数字的个数。

5、最后统计数组不为0的个数。若输入数字存在0的情况要减去。

【参考代码】

#include <iostream>using namespace std;int a[200010]={0};int main(){     int n,i,k,x,ans=0;   cin>>n;   for(i=1;i<=n;i++){      cin>>x;      a[x+100000]++;   }   for(i=0;i<=200000;i++){      if(a[i]!=0) ans++;   }   if(a[100000]!=0) ans–;   cout<<ans<<endl;  return 0;}

**【**练习3 】P13017****数组—求分数序列和

【问题描述】 

有一个分数序列 q1/p1,q2/p2,q3/p3,q4/p4,q5/p5,…. ,其中qi+1= qi+ pi, pi+1=qi, p1= 1, q1= 2。

比如这个序列前6项分别是2/1,3/2,5/3,8/5,13/8,21/13。求这个分数序列的前n项之和。

【输入格式】 

输入有一行,包含一个正整数n(n <= 1000)。

【输出格式】

输出有一行,包含一个浮点数,表示分数序列前n项的和,精确到小数点后4位。

【样例输入】

2

【样例输出】

3.5000

【说明】

数据规模:

1≤n≤30

【解题思路】

1、该题可应用题意,分别应用迭代求取分子和分母,并累积求和计算各项的累加,注意数据类型。

2、本题也可以声明两个数组,分别代表分子或分母,应用题目中表达式,循环递推计算得到,再来累积计算求和即可。

【参考代码】

#include <iostream>#include <cstdio>using namespace std; int main(){    int  n,i,j,k; float ans=0.0; float fz,fm,a; fz=2,fm=1; ans=fz/fm;     cin>>n; for(i=2;i<=n;i++){ a=fz; fz=a+fm; fm=a; ans=ans+fz/fm; } printf(“%.4f\n”,ans);}

**【**练习4 】P13019****数组— 全区间内的真素数

【问题描述】

找出正整数 M 和 N 之间(N 不小于 M)的所有真素数的个数。真素数的定义:如果一个正整数 P 为素数,且其反序也为素数,那么 P 就为真素数。例如,11,13 均为真素数,因为11的反序还是为11,13 的反序为 31 也为素数。

【输入格式】 

输入两个数 M 和 N,空格间隔

【输出格式】 

一个正整数,给定区间的真素数的个数

【样例输入】

10 35

【样例输出】

4

【说明】

样例说明:

在【10,35】之间的 11,13,17,31是真素数

数据规模:

1 <= M <= N <= 1000000。

【解题思路】

1、先用数组a将1~1000000之间的质数标志出来,注意代码技巧,很容易超时,经过下面代码的优化,标识1~1000000的质数循环的次数在3千多万次,不会超时。

2、然后按照题意从m到n之间进行搜索即可。(因搜索过程中,应用前面预先处理好的数组,不需要再对某个数进行是否质数的判断了)。

【参考代码】

#include <iostream>using namespace std;int a[1000100]={0,0,1,1};int main(){    int m,n,i,j,k,ans=0; m=1,n=1000000; for(i=3;i<1000000;i=i+2){ int f=0; for(j=3;j*j<=i;j=j+2)    if(i%j==0) {     f=1;break;    } if(f==0) a[i]=1; } for(i=m;i<=n;i++){ if(a[i]==0)continue; j=i,k=0; while(j){ k=k*10+j%10; j=j/10; } if(a[i]+a[k]==2)ans++; } cout<<ans;}

**【**练习5 】P13020****数组— 连续出现的字符

【问题描述】

给定一个字符串,在字符串中找到第一个连续出现至少k次的字符。

【输入格式】

第一行包含一个正整数k,表示至少需要连续出现的次数。1 <= k <= 1000。

第二行包含需要查找的字符串。字符串长度在1到1000之间,且不包含任何空白符。

【输出格式】

若存在连续出现至少k次的字符,输出该字符;否则输出No。

【样例输入】

3

abcccaaab

【样例输出】

c

【说明】

数据规模:

1 <= k <= 1000。

【解题思路】

1、本题实际上就是统计各个字母出现的次数,并输出第一次出现k次的字母。

2、应用数据类型转换,将字母转换成整数,即转变成出现某个整数的个数问题。

3、国际ASII码的取值范围0~127,可以定义一个数组来进行统计,下表值代表对应出现字母的ASII整数值,里面的计算代表输入的个数。循环读取字母,统计比较即可。

【参考代码】

#include <iostream>#include <cstring>#include <cstdio>using namespace std;int main(){    char a[100010]; int  b[200]={0}; char c[100]={0}; int k,i,j,n,f=0,cc=0; cin>>k; cin>>a; for(i=0;i<strlen(a);i++){ b[a[i]]++; } for(i=1;i<200;i++){ if(b[i]==k) { f=1; c[cc++]=(char)i; } } if(f==0) c[cc++]=’0′; for(int i=0;i<cc;i++)    cout<<c[i];}

成整数,即转变成出现某个整数的个数问题。

3、国际ASII码的取值范围0~127,可以定义一个数组来进行统计,下表值代表对应出现字母的ASII整数值,里面的计算代表输入的个数。循环读取字母,统计比较即可。

【参考代码】

#include <iostream>#include <cstring>#include <cstdio>using namespace std;int main(){    char a[100010]; int  b[200]={0}; char c[100]={0}; int k,i,j,n,f=0,cc=0; cin>>k; cin>>a; for(i=0;i<strlen(a);i++){ b[a[i]]++; } for(i=1;i<200;i++){ if(b[i]==k) { f=1; c[cc++]=(char)i; } } if(f==0) c[cc++]=’0′; for(int i=0;i<cc;i++)    cout<<c[i];}

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 26 天,点击查看活动详情