【蓝桥杯算法笔记】枚举

文章目录

  • G.枚举
    • 基础知识
          • 1.一般思路
          • 2.注意
    • 例题
      • 一、连号区间数
          • 1.解题思路:
          • 2.代码:
      • 二、递增三元组
          • 1.解题思路:
          • 2.代码:
      • 三、回文日期
          • 1.解题思路:
          • 2.代码:
      • 四、日期问题
          • 1.解题思路:
          • 2.代码:
          • 3.注意:

G.枚举

基础知识

1.一般思路

枚举和模拟是没有什么算法可言的,按照题目说的意思去模拟一下即可,要求对语法代码的熟练度比较高。

解题思路:一般是先想一个暴力解法,如果时间复杂度过高,再考虑一下如何去优化,一般是思考能不能减少几重几次循环。实在想不出来,直接提交暴力做法,OI赛制中也能过部分分。

2.注意

所有枚举题目都需要注意两点:

顺序:想办法去把答案枚举到,即想一个能不重不漏枚举所有答案的顺序。

优化:对代码做一些等价变形,即想有没有什么性质能优化某些步骤,从而降低时间复杂度。

例题

一、连号区间数

1.解题思路:

(1)做题前一定要注意给的数据范围,通过数据范围递推出我们要写的代码时间复杂度。本题的范围是10000,所以复杂度最好控制在O(logn)之内,O(n*n)可能也行。

(2)暴力思路:

从头开始枚举每一段序列,对每一段序列进行排序,看看他是否是单调递增序列,如果是cnt++

#include 
#include //memcpy()头文件
#include //sort()头文件
using namespace std;
const int N=10010;
int n;
int a[N];
int t[N];//辅助数组
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    
    int cnt=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=i;j<=n;j++)
        {
            memcpy(t,a,sizeof a); // 这里要把数组的初始状态存在t数组中,因为每次sort排序后,数组的顺序会发生改变。
            sort(a+i,a+j+1);//注意sort()的边界
            bool flag=true;
            for(int k=i;k<j;k++)
            {
                if(a[k+1]-a[k]!=1)
                {
                    flag=false;
                    break;
                }
            }
            if(flag)
            {
                cnt++;
            }
            memcpy(a,t,sizeof a); // 还原数组a的初始状态
        }
    }
    cout<<cnt;
    return 0;
}

注意几点:

①sort()函数的使用:

sort()有三个参数sort(begin, end, cmp),其中begin为指向待sort()的数组的第一个元素的指针,end为指向待sort()的数组的最后一个元素的下一个位置的指针,cmp参数为排序准则,cmp参数可以不写,如果不写的话,默认从小到大进行排序。

②memcpy函数的使用:

这里要把数组的初始状态存在t数组中,因为每次sort排序后,数组的顺序会发生改变。

拷贝函数,memcpy函数的功能是从源src所指的内存地址的起始位置开始拷贝n个字节到目标所指的内存地址的起始位置中。三个参数:目的地址,源地址,所需要复制的字节数。

(3)暴力做法时间复杂度O(n3logn)大于O(n2),所以只能过一部分的数据,但能得一部分得分数

(4)优化:将暴力中的sort和第三重循环优化,即如何快速的判读一个区间是不是连号区间?

排列性质:只要说是排列 ,那么序列中就不会出现重复的数

因为没有重复的数,并且区间内排序后为递增序列差值为1,因此可以转化为区间最大最小值作差等于区间长度,即max-min = j - i

区间[a,b],若区间中max-min=b-a,且区间中有b-a+1个数,则说明该区间为连号区间(递增且连续)。

枚举的同时算一下最大值和最小值,时间复杂度优化为O(n^2),虽然数据范围是10000,但是由于指令代码行数比较少,常数很小。当代码量很大时,一般常数就很大。

2.代码:
#include 
#include 
#include 

using namespace std;

const int N = 10010, INF = 100000000; 

int n;
int a[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 minv = INF, maxv = -INF;  //先定义最大值和最小值
        for (int j = i; j < n; j ++ )   // 枚举区间右端点
        {
            minv = min(minv, a[j]);
            maxv = max(maxv, a[j]); //枚举的同时算一下最大值和最小值,此时maxv和minv就是最大值和最小值
            if (maxv - minv == j - i) res ++ ;
        }
    }

    cout << res << endl;

    return 0;
}

二、递增三元组

1.解题思路:

暴力做法:时间复杂度为O(n^3)

#include 
using namespace std;
const int N=100010;
int n;
int a[N],b[N],c[N];
int main()
{
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i];
    for(int i=0;i<n;i++) cin>>b[i];
    for(int i=0;i<n;i++) cin>>c[i];
    
    int cnt=0;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<n;j++)
        {
            for(int k=0;k<n;k++)
            {
                if(a[i]<b[j]&&b[j]<c[k])
                cnt++;
            }
        }
        
    }
    cout<<cnt;
    return 0;
    
}

优化:

通过数据范围逆推出该题的时间复杂度应该为O(n*logn),所以最多只能枚举一个数组。

尝试通过枚举的次序进行优化本题,先枚举B数组,在A中寻找小于B[i]的数的个数cnt1,在C中寻找大于B[i]的数的个数cnt2,带有B[i]的合法选择数就是cnt1*cnt2。

如何查找呢?用暴力查找时间总的时间复杂度为O(n^2),还是会超时。

方法一:前缀和
1、acnt[i]表示在A中i这个值出现多少次,ccnt[i]表示在C中i这个值出现多少次

2、关键的一步,通过s[]记录当前数组从1到i中,所有数出现的次数的前缀和

3、as[]记录在A中由多少个数小于B[i],as[i] = s[b[i] - 1]

4、cs[]记录在C中由多少个数大于B[i], cs[i] = s[N - 1] - s[b[i]]

5、由于a[]和c[]互斥,通过乘法原理可得res += as[i] * cs[i]

方法二:sort+二分

方法三:双指针算法

2.代码:
//前缀和
#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 100010;

int n;
int a[N], b[N], c[N];
int as[N];  // as[i]表示在A[]中有多少个数小于b[i]
int cs[N];  // cs[i]表示在C[]中有多少个数大于b[i]
int cnt[N], s[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;
}
//sort+二分
#include 
#include 
#include 
#include 
using namespace std;

typedef long long lld;
const int N = 100005;
int a[N], b[N], c[N];
int n;
lld sum;

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++)
        scanf("%d", &b[i]);
    for (int i = 1; i <= n; i++)
        scanf("%d", &c[i]);

    //由于二分的前提是单调序列 所以预先对a b c排序 直接sort
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + n);
    sort(c + 1, c + 1 + n);

    for (int i = 1; i <= n; i++)
    {
        //直接用STL中的两个二分函数解决
        lld x = (lower_bound(a + 1, a + 1 + n, b[i]) - a) - 1;     //在数组a中找比b[i]小的数
        lld y = n - (upper_bound(c + 1, c + 1 + n, b[i]) - c) + 1; //在数组c中找比b[i]大的数
        sum += x * y;
    }

    printf("%lld", sum);
    return 0;
}

参考题解:

AcWing 1236. 递增三元组 - AcWing

AcWing 1236. 递增三元组 (二分+双指针+前缀和) - AcWing

三、回文日期

1.解题思路:

(1)数据范围八位数不是很大,所以方法有很多

考虑:①时间复杂度②空间复杂度③代码复杂度,只要在第一个和第二个能满足题意得清空下,要找一个最好写,代码量最少的做法。

我们可能会去试着暴力枚举这两个日期间的所有日期,然后再判断有多少个日期是回文的,但是这样的日期处理非常的复杂,且不确定性很高,要做很多的细节处理

(2)接着我们可以想到一个新的思路:构造枚举
①先枚举所有回文串日期:枚举所有8位数字组成的回文数(因为是回文的,所以只要枚举四位(因为不能含有前导零,所以就是枚举1000到9999)即可代替枚举8位)

②判断该回文数是否在我们考虑的范围内:直接拿数值判断即可。

③判断回文日期是否合法:如果在范围内,再判断该8位数是否是一个合法的有意义的日期

把年月日分别拆分出来,分别判断:因为枚举的是10009999,所以年份必然合法;月份要判断是不是在112之间;日子必须要>=1,并且要小于等于当月天数。最后要特判断一下是不是平年和闰年,平年和闰年的二月日子不同。

(3)注意:

如何判断日子是不是小于等于当前天数?

可以用一个数组将12个月份的天数提前存先下来,下标从1开始,利用桶的思想,直接判断这个数组对应的天数即可。(先不用管平年闰年,最后特判一下即可)

如何判断闰年?
①年份是可以被 400整除
②年份可以被4整除,并且不能被100整除

2.代码:

方法一:

时间复杂度:常数级别的,总计算量是 O(10^4)

#include
using namespace std;
int days[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};  //把1-12月的天数存下来
bool check_valid(int date)
{
    int year=date/10000;     //从date中拆分出年月日
    int month=date%10000/100;
    int day=date%100;

    if(month==0||month>12) return false;  //如果月份不合法,直接返回false

    if(day==0||(month!=2&&day>days[month])) return false;  //2月份特判,如果当前月份天数比实际的多,也不合法

    if(month==2)  //二月特判
    {
        int leap=(year%100&&year%4==0)||year%400==0; //若果是闰年的话,leap是1,否则是0
        if(day>28+leap) return  false; //这里一开始我写错了写成了!=,其实不合法的情况是出现的天数,大于是实际该有的,小于的话其实是合法的,不等于的条件判断太大了
    }
    return true;
}

int main()
{
    int date1,date2;
    cin>>date1>>date2;

    int res=0;
    for(int i=1000;i<10000;i++)   //枚举1000-9999的所有四位数,构成所有可能的8位回文序列
    {
        int date=i,x=i;
        for(int j=0;j<4;j++) date=date*10+x%10,x/=10;   //造回文数,把i反过来再接到后面去
        if(date>=date1&&date<=date2&&check_valid(date)) res++;  //符合条件的才能算
    }
    printf("%d",res);
    return 0;
}

方法二:枚举月和日,用月和日算出年和日期

时间复杂度:O(366)

#include
using namespace std;

int a, b;
long long cnt = 0;

int days[] = {-1, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

int main(){
    scanf("%d %d", &a, &b);
    for(int month = 1; month <= 12; month ++){
        for(int day = 1; day <= days[month]; day ++){
            int year = 1000 * (day % 10) + 100 * (day / 10) + 10 * (month % 10) + month / 10;
            int date = 10000 * year + 100 * month + day;
            if(date >= a && date <= b) cnt ++;
        }
    }
    printf("%d\n", cnt);
    return 0;
}

方法三:打表

可以在自己本地写一个程序 比如枚举每一个日期来判断是不是回文日期,然后把这些回文日期存下来就行了。把想要的数据输出到文件里面,然后再从文件里面粘到另一个程序中,文件输出的时候可以for循环直接输出date[%d]=%d\n 这样的格式,之后直接复制过去就行了。

#include 
using namespace std;
int date[400];
int main(){
    date[1]=10011001;
    date[2]=10100101;
    date[3]=10111101;
    date[4]=10200201;
    date[5]=10211201;
    date[6]=10300301;
    date[7]=10400401;
    date[8]=10500501;
    date[9]=10600601;
    date[10]=10700701;
    date[11]=10800801;
    date[12]=10900901;
    date[13]=11011011;
    date[14]=11100111;
    date[15]=11111111;
    date[16]=11200211;
    date[17]=11211211;
    date[18]=11300311;
    date[19]=11400411;
    date[20]=11500511;
    date[21]=11600611;
    date[22]=11700711;
    date[23]=11800811;
    date[24]=11900911;
    date[25]=12011021;
    date[26]=12100121;
    date[27]=12111121;
    date[28]=12200221;
    date[29]=12211221;
    date[30]=12300321;
    date[31]=12400421;
    date[32]=12500521;
    date[33]=12600621;
    date[34]=12700721;
    date[35]=12800821;
    date[36]=12900921;
    date[37]=13011031;
    date[38]=13100131;
    date[39]=13211231;
    date[40]=13300331;
    date[41]=13500531;
    date[42]=13700731;
    date[43]=13800831;
    date[44]=20011002;
    date[45]=20100102;
    date[46]=20111102;
    date[47]=20200202;
    date[48]=20211202;
    date[49]=20300302;
    date[50]=20400402;
    date[51]=20500502;
    date[52]=20600602;
    date[53]=20700702;
    date[54]=20800802;
    date[55]=20900902;
    date[56]=21011012;
    date[57]=21100112;
    date[58]=21111112;
    date[59]=21200212;
    date[60]=21211212;
    date[61]=21300312;
    date[62]=21400412;
    date[63]=21500512;
    date[64]=21600612;
    date[65]=21700712;
    date[66]=21800812;
    date[67]=21900912;
    date[68]=22011022;
    date[69]=22100122;
    date[70]=22111122;
    date[71]=22200222;
    date[72]=22211222;
    date[73]=22300322;
    date[74]=22400422;
    date[75]=22500522;
    date[76]=22600622;
    date[77]=22700722;
    date[78]=22800822;
    date[79]=22900922;
    date[80]=30011003;
    date[81]=30100103;
    date[82]=30111103;
    date[83]=30200203;
    date[84]=30211203;
    date[85]=30300303;
    date[86]=30400403;
    date[87]=30500503;
    date[88]=30600603;
    date[89]=30700703;
    date[90]=30800803;
    date[91]=30900903;
    date[92]=31011013;
    date[93]=31100113;
    date[94]=31111113;
    date[95]=31200213;
    date[96]=31211213;
    date[97]=31300313;
    date[98]=31400413;
    date[99]=31500513;
    date[100]=31600613;
    date[101]=31700713;
    date[102]=31800813;
    date[103]=31900913;
    date[104]=32011023;
    date[105]=32100123;
    date[106]=32111123;
    date[107]=32200223;
    date[108]=32211223;
    date[109]=32300323;
    date[110]=32400423;
    date[111]=32500523;
    date[112]=32600623;
    date[113]=32700723;
    date[114]=32800823;
    date[115]=32900923;
    date[116]=40011004;
    date[117]=40100104;
    date[118]=40111104;
    date[119]=40200204;
    date[120]=40211204;
    date[121]=40300304;
    date[122]=40400404;
    date[123]=40500504;
    date[124]=40600604;
    date[125]=40700704;
    date[126]=40800804;
    date[127]=40900904;
    date[128]=41011014;
    date[129]=41100114;
    date[130]=41111114;
    date[131]=41200214;
    date[132]=41211214;
    date[133]=41300314;
    date[134]=41400414;
    date[135]=41500514;
    date[136]=41600614;
    date[137]=41700714;
    date[138]=41800814;
    date[139]=41900914;
    date[140]=42011024;
    date[141]=42100124;
    date[142]=42111124;
    date[143]=42200224;
    date[144]=42211224;
    date[145]=42300324;
    date[146]=42400424;
    date[147]=42500524;
    date[148]=42600624;
    date[149]=42700724;
    date[150]=42800824;
    date[151]=42900924;
    date[152]=50011005;
    date[153]=50100105;
    date[154]=50111105;
    date[155]=50200205;
    date[156]=50211205;
    date[157]=50300305;
    date[158]=50400405;
    date[159]=50500505;
    date[160]=50600605;
    date[161]=50700705;
    date[162]=50800805;
    date[163]=50900905;
    date[164]=51011015;
    date[165]=51100115;
    date[166]=51111115;
    date[167]=51200215;
    date[168]=51211215;
    date[169]=51300315;
    date[170]=51400415;
    date[171]=51500515;
    date[172]=51600615;
    date[173]=51700715;
    date[174]=51800815;
    date[175]=51900915;
    date[176]=52011025;
    date[177]=52100125;
    date[178]=52111125;
    date[179]=52200225;
    date[180]=52211225;
    date[181]=52300325;
    date[182]=52400425;
    date[183]=52500525;
    date[184]=52600625;
    date[185]=52700725;
    date[186]=52800825;
    date[187]=52900925;
    date[188]=60011006;
    date[189]=60100106;
    date[190]=60111106;
    date[191]=60200206;
    date[192]=60211206;
    date[193]=60300306;
    date[194]=60400406;
    date[195]=60500506;
    date[196]=60600606;
    date[197]=60700706;
    date[198]=60800806;
    date[199]=60900906;
    date[200]=61011016;
    date[201]=61100116;
    date[202]=61111116;
    date[203]=61200216;
    date[204]=61211216;
    date[205]=61300316;
    date[206]=61400416;
    date[207]=61500516;
    date[208]=61600616;
    date[209]=61700716;
    date[210]=61800816;
    date[211]=61900916;
    date[212]=62011026;
    date[213]=62100126;
    date[214]=62111126;
    date[215]=62200226;
    date[216]=62211226;
    date[217]=62300326;
    date[218]=62400426;
    date[219]=62500526;
    date[220]=62600626;
    date[221]=62700726;
    date[222]=62800826;
    date[223]=62900926;
    date[224]=70011007;
    date[225]=70100107;
    date[226]=70111107;
    date[227]=70200207;
    date[228]=70211207;
    date[229]=70300307;
    date[230]=70400407;
    date[231]=70500507;
    date[232]=70600607;
    date[233]=70700707;
    date[234]=70800807;
    date[235]=70900907;
    date[236]=71011017;
    date[237]=71100117;
    date[238]=71111117;
    date[239]=71200217;
    date[240]=71211217;
    date[241]=71300317;
    date[242]=71400417;
    date[243]=71500517;
    date[244]=71600617;
    date[245]=71700717;
    date[246]=71800817;
    date[247]=71900917;
    date[248]=72011027;
    date[249]=72100127;
    date[250]=72111127;
    date[251]=72200227;
    date[252]=72211227;
    date[253]=72300327;
    date[254]=72400427;
    date[255]=72500527;
    date[256]=72600627;
    date[257]=72700727;
    date[258]=72800827;
    date[259]=72900927;
    date[260]=80011008;
    date[261]=80100108;
    date[262]=80111108;
    date[263]=80200208;
    date[264]=80211208;
    date[265]=80300308;
    date[266]=80400408;
    date[267]=80500508;
    date[268]=80600608;
    date[269]=80700708;
    date[270]=80800808;
    date[271]=80900908;
    date[272]=81011018;
    date[273]=81100118;
    date[274]=81111118;
    date[275]=81200218;
    date[276]=81211218;
    date[277]=81300318;
    date[278]=81400418;
    date[279]=81500518;
    date[280]=81600618;
    date[281]=81700718;
    date[282]=81800818;
    date[283]=81900918;
    date[284]=82011028;
    date[285]=82100128;
    date[286]=82111128;
    date[287]=82200228;
    date[288]=82211228;
    date[289]=82300328;
    date[290]=82400428;
    date[291]=82500528;
    date[292]=82600628;
    date[293]=82700728;
    date[294]=82800828;
    date[295]=82900928;
    date[296]=90011009;
    date[297]=90100109;
    date[298]=90111109;
    date[299]=90200209;
    date[300]=90211209;
    date[301]=90300309;
    date[302]=90400409;
    date[303]=90500509;
    date[304]=90600609;
    date[305]=90700709;
    date[306]=90800809;
    date[307]=90900909;
    date[308]=91011019;
    date[309]=91100119;
    date[310]=91111119;
    date[311]=91200219;
    date[312]=91211219;
    date[313]=91300319;
    date[314]=91400419;
    date[315]=91500519;
    date[316]=91600619;
    date[317]=91700719;
    date[318]=91800819;
    date[319]=91900919;
    date[320]=92011029;
    date[321]=92100129;
    date[322]=92111129;
    date[323]=92200229;
    date[324]=92211229;
    date[325]=92300329;
    date[326]=92400429;
    date[327]=92500529;
    date[328]=92600629;
    date[329]=92700729;
    date[330]=92800829;
    date[331]=92900929;
    int day1,day2,ans=0;
    cin>>day1>>day2;
    for(int i=1;i<=331;i++){
        if(day2>=date[i]&&day1<=date[i]) ans++;
    }
    cout<<ans<<endl;
    return 0;
}

参考题解:

AcWing 466. 回文日期 - AcWing

AcWing 466. 466题解 - AcWing

AcWing 466. 回文日期(打表) - AcWing

四、日期问题

1.解题思路:

构造枚举顺序:19600101~20591231,看成是八位数,对于每个日期拆分成年月日,判断是否合法,判断这个八位数是否和给定的表示相同,如果是输出。

枚举19600101~20591231八位数

①对于每个日期判断是否合法

②对于每个日期判断它的三种表示是否和给定表示相同

2.代码:
//构造枚举
#include //scanf头文件
#include 
#include 
#include 

using namespace std;

int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

bool check_valid(int year, int month, int day)
{
    if (month == 0 || month > 12) return false;
    if (day == 0) return false;
    if (month != 2)
    {
        if (day > days[month]) return false;
    }
    else
    {
        int leap = year % 100 && year % 4 == 0 || year % 400 == 0;//判断闰年表达式
        if (day > 28 + leap) return false;
    }

    return true;
}

int main()
{
    int a, b, c;
    scanf("%d/%d/%d", &a, &b, &c);//注意输入格式

    for (int date = 19600101; date <= 20591231; date ++ )
    {
        int year = date / 10000, month = date % 10000 / 100, day = date % 100;
        if (check_valid(year, month, day))//判断年、月、日是否合法
        {
            //判断三种表示方式是否和给定格式a,b,c相等
            if (year % 100 == a && month == b && day == c ||        // 年/月/日
                month == a && day == b && year % 100 == c ||        // 月/日/年
                day == a && month == b &&year % 100 == c)           // 日/月/年
                printf("%d-%02d-%02d\n", year, month, day);
        }
    }

    return 0;
}
3.注意:

字符串处理时习惯用scanf,printf输入输出。scanf,printf为格式化输入输出,cin,cout为流式输出

scanf("%d/%d/%d", &a, &b, &c);

sscanf是从字符串当中格式化输入,scanf是指从标准读入格式化读入,场景用的不一样,但本质上是一个东西。

printf输出时,不足补零

printf("%d-%02d-%02d\n", year, month, day);

你可能感兴趣的:(蓝桥杯专题,算法,枚举,暴力,模拟,蓝桥杯)