acwing蓝桥杯

写在前面

距离这届蓝桥杯已经过了八个月了,一直没总结算法,实在是太懒了,懒狗一条…

考前看知乎上说蓝桥杯一个月准备可以拿省一,当时还真信了,不过也可能是我有点懒,准备也不够一个月,没有专门投入太多,刷的题也不多,后面快考的时候直接摆烂了,觉得可能没啥希望,就随便看看真题了,最后就拿了个江苏CA省二,排位大概省二中上一点的样子,聊胜于无吧,没啥意思,我只想说,***退钱!!!

如果没啥竞赛基础的同学建议先看一下胡凡的《算法笔记》,这本书挺适合入门的,可以了解到算法竞赛中的大致考点,看完后就可以直接对应刷题了,如果时间不是很充足的话我个人不是很建议看紫书蓝书这种纯竞赛书,会花很多时间,而且考点是不太一样的,建议就刷acwing的蓝桥杯专题,然后多刷真题,真题就已经很多了,真题可以到官网上找,或者去一些oj也行,当时还看了罗老师的蓝桥杯每日一题https://blog.csdn.net/weixin_43914593/category_10721247.html挺不错的。不建议单纯刷力扣来准备,力扣上的用stl比较多,竞赛上更多的是用数组模拟,而且两者的侧重不同,力扣上更侧重解决的方法,只需要写solve函数就行,算法竞赛还需要自己注意输入输出格式的转换等等,当然用它复习知识点是可以的。

之前蓝桥杯搜索题很多,也“暴力杯”,但现在也越来越多动态规划题了,感觉蓝桥杯越来越难了,不太能随便混奖了,这就是内卷的结果吗…

蓝桥杯c++是可以用万能头的,而且看样例通过数给分,可以打表或者暴力骗点分,该骗的时候一定要骗,不要硬磕,也不要啥都不写(大佬直接忽略),写题的时候可以记录下思路整理到笔记,也方便以后看看,我就没写,所以下面题解都没啥讲解思路,刷题途中可能会遇到一些库的用法不太熟悉,或者一些转换的骚操作一定要单独记到笔记上面,方便复习查看。

这种编程算法还是要多敲的,一段时间不敲连二分都敲不利索,而且还有一些模板题啥的也要靠理解记忆,太长时间不敲是真不太行,手感还是很重要的,怎么说呢,个人还是更喜欢ctf和ai这种竞赛,可以翻阅笔记在线搜索啥的,不用在死板的东西上花太多时间去记忆。

明年的蓝桥杯本来也是打算报一下的,但想到四五月份可能准备考研了,时间不太充裕,这种算法又太依赖刷题和当时手感,不如就打打ctf算了。当然还有一个重要原因,300报名费还是太贵了…

第一讲 递归与递推

92. 递归实现指数型枚举

从 1∼n 这 n 个整数中随机选取任意多个,输出所有可能的选择方案。

输入格式

输入一个整数 n。

输出格式

每行输出一种方案。

同一行内的数必须升序排列,相邻两个数用恰好 1 个空格隔开。

对于没有选任何数的方案,输出空行。

本题有自定义校验器(SPJ),各行(不同方案)之间的顺序任意。

数据范围

1≤n≤15

输入样例:
3
输出样例:
3
2
2 3
1
1 3
1 2
1 2 3
#include<bits/stdc++.h>
using namespace std;
bool st[20];
int i,n;
void dfs(int x){
    if(x>n){
        for(i=1;i<=n;i++){
            if(st[i]){
                cout<<i<<" ";
            }
        }
        cout<<endl;
        return;
    }

    st[x]=true;
    dfs(x+1);
    st[x]=false;
    dfs(x+1);
}
int main(){
    cin>>n;
    dfs(1);
    return 0;
}

94. 递归实现排列型枚举

把 1∼n1∼n 这 nn 个整数排成一行后随机打乱顺序,输出所有可能的次序。

输入格式

一个整数 nn。

输出格式

按照从小到大的顺序输出所有方案,每行 11 个。

首先,同一行相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。

数据范围

1≤n≤91≤n≤9

输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include<bits/stdc++.h>
using namespace std;
int vis[20],book[20],n;
void dfs(int x){
    if(x>n){
        for(int i=1;i<=n;i++) {
            cout<<book[i]<<" ";
            
        }
        cout<<endl;
        return;
    }
        for(int i=1;i<=n;i++){
            if(vis[i]==0){
                book[x]=i;
                vis[i]=1;
                dfs(x+1);
                vis[i]=0;
            }
        }
        
    
    
}
int main(){
    cin>>n;
    dfs(1);
    return 0;
}

717. 简单斐波那契

以下数列 0 1 1 2 3 5 8 13 21 ... 被称为斐波纳契数列。

这个数列从第 33 项开始,每一项都等于前两项之和。

输入一个整数 N,请你输出这个序列的前 N 项。

输入格式

一个整数 N。

输出格式

在一行中输出斐波那契数列的前 N 项,数字之间用空格隔开。

数据范围

0<N<46

输入样例:
5
输出样例:
0 1 1 2 3
//递归带剪枝(备忘录)
#include<bits/stdc++.h>
using namespace std;
int N[50];

int fib(int n){
    if(N[n]) return N[n];
    if(n==0) return 0;
    if(n==1||n==2) return 1;
    N[n]=fib(n-1)+fib(n-2);
    return N[n];
}
int main(){
    int n;
    N[0]=0,N[1]=1,N[2]=1;
    cin>>n;
    fib(n-1);
    for(int i=0;i<n;i++) cout<<N[i]<<" ";
    return 0;
}

//不用数组直接输出
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

int main()
{
    int a = 0, b = 1;
    int n;
    cin >> n;

    for (int i = 0; i < n; i ++ )
    {
        cout << a << ' ';
        int c = a + b;
        a = b, b = c;
    }

    cout << endl;

    return 0;
}

95. 费解的开关

你玩过“拉灯”游戏吗?

25 盏灯排成一个 5×5 的方形。

每一个灯都有一个开关,游戏者可以改变它的状态。

每一步,游戏者可以改变某一个灯的状态。

游戏者改变一个灯的状态会产生连锁反应:和这个灯上下左右相邻的灯也要相应地改变其状态。

我们用数字 11 表示一盏开着的灯,用数字 00 表示关着的灯。

下面这种状态

10111
01101
10111
10000
11011

在改变了最左上角的灯的状态后将变成:

01111
11101
10111
10000
11011

再改变它正中间的灯后状态将变成:

01111
11001
11001
10100
11011

给定一些游戏的初始状态,编写程序判断游戏者是否可能在 66 步以内使所有的灯都变亮。

输入格式

第一行输入正整数 n,代表数据中共有 n 个待解决的游戏初始状态。

以下若干行数据分为 n 组,每组数据有 5 行,每行 5 个字符。

每组数据描述了一个游戏的初始状态。

各组数据间用一个空行分隔。

输出格式

一共输出 n 行数据,每行有一个小于等于 6 的整数,它表示对于输入数据中对应的游戏状态最少需要几步才能使所有灯变亮。

对于某一个游戏初始状态,若 66步以内无法使所有灯变亮,则输出 −1。

数据范围

0<n≤5000

输入样例:
3
00111
01011
10001
11010
11100

11101
11101
11110
11111
11111

01111
11111
11111
11111
11111

输出样例:

3
2
-1
//二进制模拟枚举    本行的状态都通过下一行的改变而改变

#include<bits/stdc++.h>
using namespace std;
const int N = 6;

char g[N][N], backup[N][N];
int dx[5] = {-1, 0, 1, 0, 0}, dy[5] = {0, 1, 0, -1, 0};

void turn(int x, int y)
{
    for (int i = 0; i < 5; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= 5 || b < 0 || b >= 5) continue;   // 在边界外,直接忽略即可
        g[a][b] ^= 1;//异或,同0异1,与1异或则状态改变
    }
}

int main(){
    int T;
    cin >> T;
    while (T -- )
    {
        for (int i = 0; i < 5; i ++ ) cin >> g[i];

        int res = 10;
        for (int op = 0; op < 32; op ++ ) //32为第一行所有不同的按法,
        {
            memcpy(backup, g, sizeof g);
            int step = 0;
            for (int i = 0; i < 5; i ++ )
                if (op >> i & 1)//
                {
                    step ++ ;
                    turn(0, i);
                }

            for (int i = 0; i < 4; i ++ )
                for (int j = 0; j < 5; j ++ )
                    if (g[i][j] == '0')
                    {
                        step ++ ;
                        turn(i + 1, j);//按同列的下一行即可将此处的0变为1
                    }

            bool dark = false;
            for (int i = 0; i < 5; i ++ )//如果最后一行全是1则成功
                if (g[4][i] == '0')
                {
                    dark = true;
                    break;
                }

            if (!dark) res = min(res, step);
            memcpy(g, backup, sizeof g);
        }

        if (res > 6) res = -1;

        cout << res << endl;
    }

    return 0;
}

93. 递归实现组合型枚举

从 1∼n这 n 个整数中随机选出 m 个,输出所有可能的选择方案。

输入格式

两个整数 n,m ,在同一行用空格隔开。

输出格式

按照从小到大的顺序输出所有方案,每行 1 个。

首先,同一行内的数升序排列,相邻两个数用一个空格隔开。

其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7 排在 1 3 6 8 前面)。

数据范围

n>0
0≤m≤n,
n+(n−m)≤25

输入样例:
5 3
输出样例:
1 2 3 
1 2 4 
1 2 5 
1 3 4 
1 3 5 
1 4 5 
2 3 4 
2 3 5 
2 4 5 
3 4 5 

思考题:如果要求使用非递归方法,该怎么做呢?

#include<bits/stdc++.h>
using namespace std;
int res[20];
int a,b;
void dfs(int max,int num,int n,int a){
    if(a==num) {
        for(int x=0;x<num;x++){
            cout<<res[x]<<" ";
        }
        cout<<endl;
        return;
    }
    for(int i=n;i<=max;i++){
        res[a]=i;
        dfs(max,num,i+1,a+1);
    }
}
int main(){
    
    cin>>a>>b;
    dfs(a,b,1,0);
    return 0;
}

1209. 带分数

100100 可以表示为带分数的形式:100=3+69258/714

还可以表示为:100=82+3546/197

注意特征:带分数中,数字 1∼91∼9 分别出现且只出现一次(不包含 00)。

类似这样的带分数,100100 有 1111 种表示法。

输入格式

一个正整数。

输出格式

输出输入数字用数码 1∼9 不重复不遗漏地组成带分数表示的全部种数。

数据范围

1≤N<106

输入样例1:
100
输出样例1:
11
输入样例2:
105
输出样例2:
6
#include<bits/stdc++.h>
using namespace std;
const int N=10;
int num[N];
bool used[N];
int target;
int cnt=0;
int calc(int l,int r){
    int res=0;
    for(int i=l;i<=r;i++){
        res=res*10+num[i];
    }
    return res;
}
void dfs(int s){
    if(s==9){
        for(int i=1;i<=6;i++)
            for(int j=i+1;j<=8;j++){
                int a = calc(1, i);
                int b = calc(i + 1, j);
                int c = calc(j + 1, 9);
                if(a * c + b == c * target) cnt++;
            }
            return;
    }
    for(int i=1;i<10;i++){
        if(!used[i]){
            used[i]=true;
            num[s+1]=i;
            dfs(s+1);
            used[i]=false;
        }
        
        
    }
}
int main(){
    cin>>target;
    dfs(0);
    cout<<cnt;
    return 0;
}

1208. 翻硬币

小明正在玩一个“翻硬币”的游戏。

桌上放着排成一排的若干硬币。我们用 * 表示正面,用 o 表示反面(是小写字母,不是零)。

比如,可能情形是:**oo***oooo

如果同时翻转左边的两个硬币,则变为:oooo***oooo

现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢?

我们约定:把翻动相邻的两个硬币叫做一步操作。

输入格式

两行等长的字符串,分别表示初始状态和要达到的目标状态。

输出格式

一个整数,表示最小操作步数

数据范围

输入字符串的长度均不超过100。
数据保证答案一定有解。

输入样例1:
**********
o****o****
输出样例1:
5
输入样例2:
*o**o***o***
*o***o**o***
输出样例2:
1
#include<bits/stdc++.h>//从头搜索,翻转不一样位置的后面的硬币不能对此位置造成影响,翻前一个则会打乱前面相同的字符,产生新的不同字符,所以最优就是翻此硬币(贪心)
using namespace std;
int main(){
    string start,target;
    int step=0;
    cin>>start>>target;
    int n=start.size();
    for(int i=0;i<n-1;i++){
        if(start[i]!=target[i]){
            if(start[i]=='*') start[i]='o';
            else start[i]='*';
            if(start[i+1]=='*') start[i+1]='o';
            else start[i+1]='*';
            step++;
        }
    }
    cout<<step;
    return 0;
}

116. 飞行员兄弟

“飞行员兄弟”这个游戏,需要玩家顺利的打开一个拥有 16 个把手的冰箱。

已知每个把手可以处于以下两种状态之一:打开或关闭。

只有当所有把手都打开时,冰箱才会打开。

把手可以表示为一个 4×4的矩阵,您可以改变任何一个位置 [i,j][i,j] 上把手的状态。

但是,这也会使得第 i 行和第 jj列上的所有把手的状态也随着改变。

请你求出打开冰箱所需的切换把手的次数最小值是多少。

输入格式

输入一共包含四行,每行包含四个把手的初始状态。

符号 + 表示把手处于闭合状态,而符号 - 表示把手处于打开状态。

至少一个手柄的初始状态是关闭的。

输出格式

第一行输出一个整数 N,表示所需的最小切换把手次数。

接下来 N 行描述切换顺序,每行输出两个整数,代表被切换状态的把手的行号和列号,数字之间用空格隔开。

注意:如果存在多种打开冰箱的方式,则按照优先级整体从上到下,同行从左到右打开。

数据范围

1≤i,j≤4

输入样例:

-+--
----
----
-+--
输出样例:
6
1 1
1 3
1 4
4 1
4 3
4 4
#include<bits/stdc++.h>
using namespace std;

typedef pair<int,int> PII;
const int N = 4, INF = 100;

int change[N][N];

int get(int x, int y)
{
    return x * 4 + y;
}



int main(){
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
        {
            for (int k = 0; k < N; k ++ ) change[i][j] += (1 << get(i, k)) + (1 << get(k, j));
            change[i][j] -= 1 << get(i, j);
        }
    int state = 0;
    for (int i = 0; i < N; i ++ )
    {
        string line;
        cin >> line;
        for (int j = 0; j < N; j ++ )
            if (line[j] == '+')
                state += 1 << get(i, j);
    }
    vector<PII> path;
    for (int i = 0; i < 1 << 16; i ++ )//枚举按下的所有情况(位置一样即可,先后顺序不影响)
    {
        int now = state;
        vector<PII> temp;
        for (int j = 0; j < 16; j ++ )
            if (i >> j & 1)
            {
                int x = j / 4, y = j % 4;
                now ^= change[x][y];
                temp.push_back({x, y});
            }
        if (!now && (path.empty() || path.size() > temp.size())) path = temp;
    }

    cout << path.size() << endl;
    for (auto &p : path)
        cout << p.first + 1 << ' ' << p.second + 1 << endl;

    return 0;
}

第二讲 二分与前缀和

789. 数的范围

给定一个按照升序排列的长度为 n的整数数组,以及 q个查询。

对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。

如果数组中不存在该元素,则返回 -1 -1

输入格式

第一行包含整数 n 和 q,表示数组长度和询问个数。

第二行包含 n 个整数(均在 1∼10000 范围内),表示完整数组。

接下来 q 行,每行包含一个整数 k,表示一个询问元素。

输出格式

共 q 行,每行包含两个整数,表示所求元素的起始位置和终止位置。

如果数组中不存在该元素,则返回 -1 -1

数据范围

1≤n≤100000
1≤q≤10000
1≤k≤10000

输入样例:
6 3
1 2 2 3 3 4
3
4
5
输出样例:
3 4
5 5
-1 -1
难度:简单
时/空限制:1s / 64MB
总通过数:32991
总尝试数:54013
来源:模板题,AcWing
算法标签二分
#include<iostream>
using namespace std;

const int N=100010;
int q[N];
int main(){
    int n,c,x;
    cin>>n>>c;
    for(int i=0;i<n;i++) cin>>q[i];
    while(c--){
        cin>>x;
        int l=0,r=n-1;
        while(l<r){
            int mid=l+r>>1;
            if(q[mid]<x) l=mid+1;
            else r=mid;   
        }
    if(q[l]!=x){
        cout<<"-1 -1"<<endl;
        continue;
    }
    int l1 = l, r1 = n-1;
    while(l1<r1){
        int mid=l1+r1+1>>1;
        if(q[mid]<=x) l1=mid;
        else r1=mid-1;   
    }
    cout<<l<<" "<<l1<<endl;
    }
    return 0;
}
整数二分算法模板 —— 模板题 AcWing 789. 数的范围
bool check(int x) {/* ... */} // 检查x是否满足某种性质

// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:
int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;    // check()判断mid是否满足性质
        else l = mid + 1;
    }
    return l;
}
// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:
int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}
浮点数二分算法模板 —— 模板题 AcWing 790. 数的三次方根
bool check(double x) {/* ... */} // 检查x是否满足某种性质

double bsearch_3(double l, double r)
{
    const double eps = 1e-6;   // eps 表示精度,取决于题目对精度的要求
    while (r - l > eps)
    {
        double mid = (l + r) / 2;
        if (check(mid)) r = mid;
        else l = mid;
    }
    return l;
}

790. 数的三次方根

给定一个浮点数 n,求它的三次方根。

输入格式

共一行,包含一个浮点数 n。

输出格式

共一行,包含一个浮点数,表示问题的解。

注意,结果保留 6 位小数。

数据范围

−10000≤n≤10000

输入样例:
1000.00
输出样例:
10.000000
难度:简单
时/空限制:1s / 64MB
总通过数:23870
总尝试数:45712
来源:模板题,AcWing
算法标签二分
#include<iostream>
#include<iomanip>
using namespace std;
double l,r,mid,n;
double thr(double n){
	return n*n*n;
}
int main(){
	cin>>n;
	l=-25, r=25;
	while(r-l>1e-7){
		mid=(l+r)/2;
		if(thr(mid)>=n) r=mid;
		else l=mid;
	}
	cout<<fixed<<setprecision(6)<<l;
	return 0;
} 

730. 机器人跳跃问题

机器人正在玩一个古老的基于 DOS 的游戏。

游戏中有 N+1座建筑——从 0 到 N 编号,从左到右排列。

编号为 0 的建筑高度为 0 个单位,编号为 i 的建筑高度为 H(i) 个单位。

起初,机器人在编号为 0 的建筑处。

每一步,它跳到下一个(右边)建筑。

假设机器人在第 k 个建筑,且它现在的能量值是 E,下一步它将跳到第k+1 个建筑。

如果 H(k+1)>E,那么机器人就失去 H(k+1)−E的能量值,否则它将得到 E−H(k+1) 的能量值。

游戏目标是到达第 N 个建筑,在这个过程中能量值不能为负数个单位。

现在的问题是机器人至少以多少能量值开始游戏,才可以保证成功完成游戏?

输入格式

第一行输入整数 N。

第二行是 N 个空格分隔的整数,H(1),H(2),…,H(N) 代表建筑物的高度。

输出格式

输出一个整数,表示所需的最少单位的初始能量值上取整后的结果。

数据范围

1≤N,H(i)≤10^5

输入样例1:
5
3 4 3 2 4
输出样例1:
4
输入样例2:
3
4 4 4
输出样例2:
4
输入样例3:
3
1 6 4
输出样例3:
3
难度:中等
时/空限制:3s / 64MB
总通过数:4344
总尝试数:9691
来源:今日头条2019,笔试题
算法标签二分递推
#include<bits/stdc++.h>
using namespace std;

const int N=100010;
int n;
int h[N];

bool check(int mid){
    for(int i=1;i<=n;i++){
        mid+=mid-h[i];
        if (mid>= 1e5) return true; //防止int溢出
        if(mid<0) return false;
    }
    return true;
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>h[i];
    }
    int l=0,r=1e5;
    while(l<r){
        int mid=l+r>>1;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    
    cout<<l;
    return 0;
}

1221. 四平方和

四平方和定理,又称为拉格朗日定理:

每个正整数都可以表示为至多 4 个正整数的平方和。

如果把 00 包括进去,就正好可以表示为 4 个数的平方和。

比如:

5=02+02+12+22
7=12+12+12+22

对于一个给定的正整数,可能存在多种平方和的表示法。

要求你对 4 个数排序:

0≤a≤b≤c≤d

并对所有的可能表示法按 a,b,c,d 为联合主键升序排列,最后输出第一个表示法。

输入格式

输入一个正整数 N。

输出格式

输出4个非负整数,按从小到大排序,中间用空格分开。

数据范围

0<N<5∗1060<N<5∗106

输入样例:
5
输出样例:
0 0 1 2
难度:简单
时/空限制:1s / 64MB
总通过数:3882
总尝试数:14618
来源:第七届蓝桥杯省赛C++A/B组,第七届蓝桥杯省赛JAVAB/C组
算法标签二分 哈希
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 2500010;

struct Sum
{
    int s, c, d;
    bool operator< (const Sum &t)const  //重定义后可使用sort
    {
        if (s != t.s) return s < t.s;
        if (c != t.c) return c < t.c;
        return d < t.d;
    }
}sum[N];

int n, m;

int main()
{
    cin >> n;

    for (int c = 0; c * c <= n; c ++ )
        for (int d = c; c * c + d * d <= n; d ++ )
            sum[m ++ ] = {c * c + d * d, c, d};

    sort(sum, sum + m);

    for (int a = 0; a * a <= n; a ++ )
        for (int b = 0; a * a + b * b <= n; b ++ )
        {
            int t = n - a * a - b * b;
            int l = 0, r = m - 1;
            while (l < r)
            {
                int mid = l + r >> 1;
                if (sum[mid].s >= t) r = mid;
                else l = mid + 1;
            }
            if (sum[l].s == t)
            {
                printf("%d %d %d %d\n", a, b, sum[l].c, sum[l].d);
                return 0;
            }
        }

    return 0;
}

1227. 分巧克力

儿童节那天有 K 位小朋友到小明家做客。

小明拿出了珍藏的巧克力招待小朋友们。

小明一共有 N 块巧克力,其中第 ii 块是 Hi×Wi的方格组成的长方形。

为了公平起见,小明需要从这 N 块巧克力中切出 K 块巧克力分给小朋友们。

切出的巧克力需要满足:

  1. 形状是正方形,边长是整数
  2. 大小相同

例如一块 6×5的巧克力可以切出 6 块 2×2的巧克力或者 2 块 3×3的巧克力。

当然小朋友们都希望得到的巧克力尽可能大,你能帮小明计算出最大的边长是多少么?

输入格式

第一行包含两个整数 N 和 K。

以下 N 行每行包含两个整数 Hi和 Wi。

输入保证每位小朋友至少能获得一块 1×1 的巧克力。

输出格式

输出切出的正方形巧克力最大可能的边长。

数据范围

1≤N,K≤10^5
1≤Hi,Wi≤10^5

输入样例:
2 10
6 5
5 6
输出样例:
2
难度:简单
时/空限制:1s / 64MB
总通过数:7643
总尝试数:15754
来源:第八届蓝桥杯省赛C++A/B组,第八届蓝桥杯省赛JAVAA/B组
算法标签二分
#include<bits/stdc++.h>
using namespace std;
const int N=100010;


int h[N],w[N];

int n,k;

bool check(int mid){
    int res=0;
    for(int i=0;i<n;i++){
        res+=(h[i]/mid)*(w[i]/mid);
    }
    if(res>=k) return true;
    else return false;
}

int main(){
    ios::sync_with_stdio(false);
    cin>>n>>k;
    for(int i=0;i<n;i++){
        cin>>h[i]>>w[i];
    }
    int l=0,r=1e5;
    while(l<r){
        int mid=l+r+1>>1;
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    cout<<l;
    return 0;
}

99. 激光炸弹

地图上有 N 个目标,用整数 Xi,Yi表示目标在地图上的位置,每个目标都有一个价值 Wi。

注意:不同目标可能在同一位置。

现在有一种新型的激光炸弹,可以摧毁一个包含 R×R 个位置的正方形内的所有目标。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆炸范围,即那个正方形的边必须和 x,y 轴平行。

求一颗炸弹最多能炸掉地图上总价值为多少的目标。

输入格式

第一行输入正整数 N 和 R,分别代表地图上的目标数目和正方形的边长,数据用空格隔开。

接下来 N 行,每行输入一组数据,每组数据包括三个整数 Xi,Yi,Wi,分别代表目标的 x 坐标,y 坐标和价值,数据用空格隔开。

输出格式

输出一个正整数,代表一颗炸弹最多能炸掉地图上目标的总价值数目。

数据范围

0≤R≤10^9
0<N≤10000
0≤Xi,Yi≤5000
0≤Wi≤1000

输入样例:
2 1
0 0 1
1 1 1
输出样例:
1
难度:简单
时/空限制:1s / 168MB
总通过数:8583
总尝试数:25487
来源:《算法竞赛进阶指南》, HNOI2003
算法标签二维前缀和
#include <iostream>
using namespace std;

int g[5010][5010];//价值
int N,R;

int main()
{
    cin>>N>>R;
    int n=R,m=R;//长和宽
    for(int i=0,x,y,w;i<N;i++)
    {
        cin>>x>>y>>w;
        x++;
        y++;
        //为了方便处理边界,把前缀和定义从1开始,所以开头要++
        n=max(n,x);
        m=max(m,y);
        //判断长和宽的边界
        g[x][y]+=w;//将他们的价值放进数组里
    }
    //输入&预处理
    n = 5001, m = 5001, R = min(R, 5001);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            g[i][j]+=g[i-1][j] + g[i][j-1] -g[i-1][j-1];
            //g[i-1][j]=sum1,g[i][j-1]=sum2,g[i-1][j-1]=sum3
        }
    }
    //求二维前缀和

    int ans=0;
    for(int i=R;i<=n;i++)//长
    {
        for(int j=R;j<=m;j++)//宽
        {
            ans=max(ans,g[i][j] - g[i-R][j] - g[i][j-R] +g[i-R][j-R]);
        }
    }
    //求最大值

    cout<<ans<<endl;

    return 0;
}

1230. K倍区间

给定一个长度为 N 的数列,A1,A2,…AN,如果其中一段连续的子序列 Ai,Ai+1,…Aj 之和是 K 的倍数,我们就称这个区间 [i,j][i,j] 是 K 倍区间。

你能求出数列中总共有多少个 K 倍区间吗?

输入格式

第一行包含两个整数 N 和 K。

以下 N 行每行包含一个整数 Ai。

输出格式

输出一个整数,代表 K 倍区间的数目。

数据范围

1≤N,K≤100000
1≤Ai≤100000

输入样例:
5 2
1
2
3
4
5
输出样例:
6
难度:中等
时/空限制:1s / 64MB
总通过数:4754
总尝试数:12418
来源:第八届蓝桥杯省赛C++B组,第八届蓝桥杯省赛JAVAB组
算法标签前缀和
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, k;
LL s[N], cnt[N];

int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%lld", &s[i]);
        s[i] += s[i - 1];
    }

    LL res = 0;
    cnt[0] = 1;
    for (int i = 1; i <= n; i ++ )
    {
        res += cnt[s[i] % k];
        cnt[s[i] % k] ++ ;
    }

    printf("%lld\n", res);

    return 0;
}

第三讲 数学与简单DP

1205. 买不到的数目

小明开了一家糖果店。

他别出心裁:把水果糖包成4颗一包和7颗一包的两种。

糖果不能拆包卖。

小朋友来买糖的时候,他就用这两种包装来组合。

当然有些糖果数目是无法组合出来的,比如要买 10 颗糖。

你可以用计算机测试一下,在这种包装情况下,最大不能买到的数量是17。

大于17的任何数字都可以用4和7组合出来。

本题的要求就是在已知两个包装的数量时,求最大不能组合出的数字。

输入格式

两个正整数 n,m表示每种包装中糖的颗数。

输出格式

一个正整数,表示最大不能买到的糖数。

数据范围

2≤n,m≤1000
保证数据一定有解。

输入样例:
4 7
输出样例:
17
难度:简单
时/空限制:1s / 64MB
总通过数:3821
总尝试数:6647
来源:第四届蓝桥杯省赛C++A组,第四届蓝桥杯省赛JAVAC组
算法标签数论结论题
//数论题,可打表找规律
#include <iostream>

using namespace std;

int main()
{
    int p, q;
    cin >> p >> q;
    cout << (p - 1) * (q - 1) - 1 << endl;

    return 0;
}

1211. 蚂蚁感冒

长 100厘米的细长直杆子上有 n 只蚂蚁。

它们的头有的朝左,有的朝右。

每只蚂蚁都只能沿着杆子向前爬,速度是 1 厘米/秒。

当两只蚂蚁碰面时,它们会同时掉头往相反的方向爬行。

这些蚂蚁中,有 1 只蚂蚁感冒了。

并且在和其它蚂蚁碰面时,会把感冒传染给碰到的蚂蚁。

请你计算,当所有蚂蚁都爬离杆子时,有多少只蚂蚁患上了感冒。

输入格式

第一行输入一个整数 n, 表示蚂蚁的总数。

接着的一行是 n 个用空格分开的整数 XiXi, 的绝对值表示蚂蚁离开杆子左边端点的距离。

正值表示头朝右,负值表示头朝左,数据中不会出现 0 值,也不会出现两只蚂蚁占用同一位置。

其中,第一个数据代表的蚂蚁感冒了。

输出格式

输出1个整数,表示最后感冒蚂蚁的数目。

数据范围

1<n<50
0<|Xi|<100

输入样例1:
3
5 -2 8
输出样例1:
1
输入样例2:
5
-10 8 -20 12 25
输出样例2:
3
难度:简单
时/空限制:1s / 64MB
总通过数:2799
总尝试数:5471
来源:第五届蓝桥杯省赛C++A/B组
算法标签数学
//由于碰面后速度都不变,可以看作感染后不掉头,还是按原来的方向走
#include<bits/stdc++.h>
using namespace std;
const int N = 55;
int x[N];
int main(){
    ios::sync_with_stdio(false);
    int n;
    cin>>n;
    for(int i=0;i<n;i++){
        cin>>x[i];
    }
    int res=1;
    int flag=0;
    int right=0;
    if(x[0]>0){
        for(int i=1;i<n;i++){
            if(x[i]<0&&abs(x[i])>x[0]){
                res++;
                flag=1;
            }
            if(x[i]>0&&x[i]<x[0]) right++;
        }
        if(flag) res+=right;
    }else{
        for(int i=1;i<n;i++){
            if(x[i]>0&&x[i]<abs(x[0])){
                res++;
                flag=1;
            }
            if(x[i]<0&&x[i]<x[0]) right++;
        }
        if(flag) res+=right;
    }
    cout<<res;
    return 0;
}

1216. 饮料换购

乐羊羊饮料厂正在举办一次促销优惠活动。乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去(但不允许暂借或赊账)。

请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的 n 瓶饮料,最后他一共能喝到多少瓶饮料。

输入格式

输入一个整数 n,表示初始买入的饮料数量。

输出格式

输出一个整数,表示一共能够喝到的饮料数量。

数据范围

0<n<10000

输入样例:
100
输出样例:
149
难度:简单
时/空限制:1s / 64MB
总通过数:2875
总尝试数:4171
来源:第六届蓝桥杯省赛C++A/C组,第六届蓝桥杯省赛JAVAB组
算法标签数学
#include <iostream>

using namespace std;

int main()
{
    int n;
    cin >> n;

    int res = n;
    while (n >= 3)
    {
        res += n / 3;
        n = n / 3 + n % 3;
    }

    cout << res << endl;

    return 0;
}

2. 01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 ii 件物品的体积是 vi,价值是 wi。

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。

输入格式

第一行两个整数,N,V用空格隔开,分别表示物品数量和背包容积。

接下来有 NN 行,每行两个整数 vi,wi,用空格隔开,分别表示第 ii 件物品的体积和价值。

输出格式

输出一个整数,表示最大价值。

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8
难度:简单
时/空限制:1s / 64MB
总通过数:59912
总尝试数:101193
来源:背包九讲 , 模板题
算法标签背包问题DP
#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int dp[N][N],val[N],r[N];
int n,v;

int main(){
    cin>>n>>v;
    for(int i=1;i<=n;i++){
        cin>>r[i]>>val[i];
    }
    for(int i=1;i<=n;i++){
        for(int w=1;w<=v;w++){
            if(w-r[i]<0) dp[i][w]=dp[i-1][w];
            else{
                dp[i][w]=max(dp[i-1][w],dp[i-1][w-r[i]]+val[i]);
            }
        }
    }
    cout<<dp[n][v];
    return 0;
}

1015. 摘花生

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。

1.gif

输入格式

第一行是一个整数T,代表一共有多少组数据。

接下来是T组数据。

每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。

每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。

输出格式

对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。

数据范围

1≤T≤100
1≤R,C≤100
0≤M≤1000

输入样例:
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出样例:
8
16
难度:简单
时/空限制:1s / 64MB
总通过数:9825
总尝试数:12373
来源:《信息学奥赛一本通》
算法标签DP线性DP
#include<bits/stdc++.h>
using namespace std;
const int N=110;

int s[N][N],dp[N][N];


int main(){
    int t;
    cin>>t;
    while(t--){
        int m,n;
        cin>>m>>n;
        for(int i=1;i<=m;i++)
            for(int j=1;j<=n;j++){
                cin>>s[i][j];
            }
    
    for(int i=1;i<=m;i++)
        for(int j=1;j<=n;j++){
            dp[i][j]=max(dp[i-1][j],dp[i][j-1])+s[i][j];
        }
        cout<<dp[m][n]<<endl;
    
    }
    return 0;
}

895. 最长上升子序列

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式

第一行包含整数 N。

第二行包含 N 个整数,表示完整序列。

输出格式

输出一个整数,表示最大长度。

数据范围

1≤N≤1000
−109≤数列中的数≤109

输入样例:
7
3 1 2 1 8 5 6
输出样例:
4
难度:简单
时/空限制:1s / 64MB
总通过数:16296
总尝试数:24724
来源:模板题,AcWing
算法标签动态规划线性DP最长上升子序列
#include<bits/stdc++.h>
using namespace std;

const int N=1010;
int dp[N],s[N];
int n;

int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>s[i];
    }
    dp[1]=1;
    int res=0;
    for(int i=2;i<=n;i++){
        dp[i]=1;
        for(int j=1;j<i;j++){
            if(s[j]<s[i]) dp[i]=max(dp[i],dp[j]+1);
            res=max(res,dp[i]);
        }
    }
    cout<<res;
    return 0;
}

1212. 地宫取宝

X 国王有一个地宫宝库,是 n×m 个格子的矩阵,每个格子放一件宝贝,每个宝贝贴着价值标签。

地宫的入口在左上角,出口在右下角。

小明被带到地宫的入口,国王要求他只能向右或向下行走。

走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。

当小明走到出口时,如果他手中的宝贝恰好是 k 件,则这些宝贝就可以送给小明。

请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这 k 件宝贝。

输入格式

第一行 3 个整数,n,m,k,含义见题目描述。

接下来 n 行,每行有 m 个整数 Ci 用来描述宝库矩阵每个格子的宝贝价值。

输出格式

输出一个整数,表示正好取 k 个宝贝的行动方案数。

该数字可能很大,输出它对 1000000007取模的结果。

数据范围

1≤n,m≤50
1≤k≤12
0≤Ci≤12

输入样例1:
2 2 2
1 2
2 1
输出样例1:
2
输入样例2:
2 3 2
1 2 3
2 1 5
输出样例2:
14
难度:中等
时/空限制:1s / 64MB
总通过数:2566
总尝试数:5791
来源:第五届蓝桥杯省赛C++A/B/C组,第五届蓝桥杯省赛JAVAB/C组
算法标签动态规划DP

1214. 波动数列

观察这个数列:

1 3 0 2 -1 1 -2 …

这个数列中后一项总是比前一项增加2或者减少3,且每一项都为整数

栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加 a 或者减少 b 的整数数列可能有多少种呢?

输入格式

共一行,包含四个整数 n,s,a,b含义如前面所述。

输出格式

共一行,包含一个整数,表示满足条件的方案数。

由于这个数很大,请输出方案数除以 100000007的余数。

数据范围

1≤n≤1000
−109≤s≤109
1≤a,b≤10^6

输入样例:
4 10 2 3
输出样例:
2
样例解释

两个满足条件的数列分别是2 4 1 3和7 4 1 -2。

难度:中等
时/空限制:1s / 64MB
总通过数:1471
总尝试数:3269
来源:第五届蓝桥杯省赛C++A组,第五届蓝桥杯省赛JAVAA组
算法标签动态规划DP
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010, MOD = 100000007;

int f[N][N];

int get_mod(int a, int b)   // 求a除以b的正余数
{
    return (a % b + b) % b;
}

int main()
{
    int n, s, a, b;
    cin >> n >> s >> a >> b;

    f[0][0] = 1;
    for (int i = 1; i < n; i ++ )
        for (int j = 0; j < n; j ++ )
            f[i][j] = (f[i - 1][get_mod(j - a * (n - i), n)] + f[i - 1][get_mod(j + b * (n - i), n)]) % MOD;

    cout << f[n - 1][get_mod(s, n)] << endl;//因为d只有n-1个,所以输出f[n - 1][get_mod(s, n)]

    return 0;
}

第四讲 枚举、模拟与排序

1210. 连号区间数

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

在 1∼N 的某个排列中有多少个连号区间呢?

这里所说的连号区间的定义是:

如果区间 [L,R][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
样例解释

第一个用例中,有 77 个连号区间分别是:[1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4]
第二个用例中,有 99 个连号区间分别是:[1,1],[1,2],[1,3],[1,4],[1,5],[2,2],[3,3],[4,4],[5,5]

难度:简单
时/空限制:1s / 64MB
总通过数:2966
总尝试数:5251
来源:第四届蓝桥杯省赛C++B组,第四届蓝桥杯省赛JAVAB组
算法标签枚举
#include <cstring>
#include <iostream>
#include <algorithm>

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]);
            if (maxv - minv == j - i) res ++ ;
        }
    }

    cout << res << endl;

    return 0;
}

1236. 递增三元组

给定三个整数数组

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

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

  1. 1≤i,j,k≤N
  2. Ai<Bj<Ck
输入格式

第一行包含一个整数 N。

第二行包含 NN 个整数 A1,A2,…AN。

第三行包含 NN 个整数 B1,B2,…BN。

第四行包含 NN 个整数 C1,C2,…CN。

输出格式

一个整数表示答案。

数据范围

1≤N≤10^5
0≤Ai,Bi,Ci≤10^5

输入样例:
3
1 1 1
2 2 2
3 3 3
输出样例:
27
难度:中等
时/空限制:1s / 64MB
总通过数:4064
总尝试数:14305
来源:第九届蓝桥杯省赛C++B组,第九届蓝桥杯省赛JAVAB组
算法标签枚举二分前缀和双指针
#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N=100010;
int n,a[N],b[N],c[N],cnt[N],as[N],cs[N],s[N];

int main(){
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=0;i<n;i++) cin>>a[i],a[i]++;//有可能为0,下面求数组下标时有减1,所以++
    for(int i=0;i<n;i++) cin>>b[i],b[i]++;
    for(int i=0;i<n;i++) cin>>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]];
    LL res=0;
    for(int i=0;i<n;i++){
        res+=(LL)as[i]*cs[i];
    }
    cout<<res;
    return 0;
}

1245. 特别数的和

小明对数位中含有 2、0、1、9 的数字很感兴趣(不包括前导 0),在 1 到 40 中这样的数包括 1、2、9、10至 32、39 和 40,共 28 个,他们的和是v574。

请问,在 1 到 n 中,所有这样的数的和是多少?

输入格式

共一行,包含一个整数 n。

输出格式

共一行,包含一个整数,表示满足条件的数的和。

数据范围

1≤n≤10000

输入样例:
40
输出样例:
574
难度:简单
时/空限制:1s / 64MB
总通过数:2833
总尝试数:3398
来源:第十届蓝桥杯省赛C++B组,第十届蓝桥杯省赛JAVAB组
算法标签模拟
#include<bits/stdc++.h>
using namespace std;

bool check(int x){
    while(x>0){
        int temp=x%10;
        if(temp==2||temp==0||temp==1||temp==9) return true;
        else{
            x/=10;
        }
    }
    return false;
}

int main(){
    int n;
    cin>>n;
    int res=0;
    for(int i=1;i<=n;i++){
        if(check(i)) res+=i;
    }
    cout<<res;
    return 0;
}

1204. 错误票据

某涉密单位下发了某种票据,并要在年终全部收回。

每张票据有唯一的ID号。

全年所有票据的ID号是连续的,但ID的开始数码是随机选定的。

因为工作人员疏忽,在录入ID号的时候发生了一处错误,造成了某个ID断号,另外一个ID重号。

你的任务是通过编程,找出断号的ID和重号的ID。

假设断号不可能发生在最大和最小号。

输入格式

第一行包含整数 N,表示后面共有 N 行数据。

接下来 N 行,每行包含空格分开的若干个(不大于100个)正整数(不大于100000),每个整数代表一个ID号。

输出格式

要求程序输出1行,含两个整数 m,n用空格分隔。

其中,m表示断号ID,n表示重号ID。

数据范围

1≤N≤100

输入样例:
2
5 6 8 11 9 
10 12 9
输出样例:
7 9
难度:简单
时/空限制:1s / 64MB
总通过数:4114
总尝试数:7691
来源:第四届蓝桥杯省赛C++A/B组,第四届蓝桥杯省赛JAVAA/B组
算法标签排序模拟
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=10010,M=100010;

int cnt[M];

int main(){
    int line,i=0,Min=100001,Max=0,a;
    int n,m; //m表示断号ID,n表示重号ID
    cin>>line;
    while(line--){
        while(cin>>a){
            cnt[a]++;
            Max=max(Max,a);
            Min=min(Min,a);
        }
    }

    for(int j=Min;j<Max;j++){
        if(cnt[j]==0) m=j;
        else if(cnt[j]==2) n=j;
    }

    cout<<m<<" "<<n<<endl;
    return 0;
}

466. 回文日期

在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。

牛牛习惯用 8 位数字表示一个日期,其中,前 4 位代表年份,接下来 2 位代表月份,最后 2 位代表日期。

显然:一个日期只有一种表示方法,而两个不同的日期的表示方法不会相同。

牛牛认为,一个日期是回文的,当且仅当表示这个日期的 8 位数字是回文的。

现在,牛牛想知道:在他指定的两个日期之间(包含这两个日期本身),有多少个真实存在的日期是回文的。

一个 88 位数字是回文的,当且仅当对于所有的 ii(1≤i≤8) 从左向右数的第 i 个数字和第 9−i个数字(即从右向左数的第 i 个数字)是相同的。

例如:

  • 对于 2016 年 11 月 19 日,用 8 位数字 20161119表示,它不是回文的。
  • 对于 2010 年 1 月 2 日,用 8 位数字 20100102 表示,它是回文的。
  • 对于 2010年 10 月 22日,用 8 位数字 20101002表示,它不是回文的。
输入格式

输入包括两行,每行包括一个 8 位数字。

第一行表示牛牛指定的起始日期 date1,第二行表示牛牛指定的终止日期 date2。保证 date1 和 date2都是真实存在的日期,且年份部分一定为 4 位数字,且首位数字不为 0。

保证 date1 一定不晚于 date2。

输出格式

输出共一行,包含一个整数,表示在 date1 和 date2之间,有多少个日期是回文的。

输入样例:
20110101
20111231
输出样例:
1
难度:简单
时/空限制:1s / 64MB
总通过数:2657
总尝试数:6134
来源:NOIP2016普及组
算法标签枚举模拟

787. 归并排序

给定你一个长度为 n 的整数数列。

请你使用归并排序对这个数列按照从小到大进行排序。

并将排好序的数列按顺序输出。

输入格式

输入共两行,第一行包含整数 n。

第二行包含 n 个整数(所有整数均在 1∼109 范围内),表示整个数列。

输出格式

输出共一行,包含 n 个整数,表示排好序的数列。

数据范围

1≤n≤100000

输入样例:
5
3 1 2 4 5
输出样例:
1 2 3 4 5
难度:简单
时/空限制:1s / 64MB
总通过数:40064
总尝试数:59738
来源:模板题,AcWing
算法标签归并排序
#include<iostream>
using namespace std;

const int N=1e6+10;
int q[N],temp[N];

void merge_sort(int q[],int l,int r){
    
    if(l>=r) return;
    int mid=l+r>>1;
    merge_sort(q,l,mid),merge_sort(q,mid+1,r);
    int k=0,i=l,j=mid+1;
    while(i<=mid&&j<=r){
        if(q[i]<q[j]) temp[k++]=q[i++];
        else temp[k++]=q[j++];
    }
    while(i<=mid) temp[k++]=q[i++];
    while(j<=r) temp[k++]=q[j++];
    for(int i=l,j=0;i<=r;i++,j++) q[i]=temp[j];
}

int main(){
    int n;
    scanf("%d",&n);
    for(int i;i<n;i++) scanf("%d",&q[i]);
    merge_sort(q,0,n-1);
    for(int i;i<n;i++) printf("%d ",q[i]);
    return 0;
}

1219. 移动距离

X星球居民小区的楼房全是一样的,并且按矩阵样式排列。

其楼房的编号为 1,2,3…

当排满一行时,从下一行相邻的楼往反方向排号。

比如:当小区排号宽度为 6时,开始情形如下:

1  2  3  4  5  6
12 11 10 9  8  7
13 14 15 .....

我们的问题是:已知了两个楼号 m 和 n,需要求出它们之间的最短移动距离(不能斜线方向移动)。

输入格式

输入共一行,包含三个整数 w,m,n,w 为排号宽度,m,n 为待计算的楼号。

输出格式

输出一个整数,表示 m,n两楼间最短移动距离。

数据范围

1≤w,m,n≤10000

输入样例:
6 8 2
输出样例:
4
难度:简单
时/空限制:1s / 64MB
总通过数:2579
总尝试数:6206
来源:第六届蓝桥杯省赛C++B组,第六届蓝桥杯省赛JAVAA/C组
算法标签模拟
#include<bits/stdc++.h>
using namespace std;

int main(){
    int w,n,m;
    cin>>w>>n>>m;
    m--,n--;
    int x1=n/w,x2=m/w;
    int y1=n%w,y2=m%w;
    if(x1%2) y1=w-y1-1;
    if(x2%2) y2=w-y2-1;
    cout<<abs(x1-x2)+abs(y1-y2);
    return 0;
}

1229. 日期问题

小明正在整理一批历史文献。这些历史文献中出现了很多日期。

小明知道这些日期都在1960年1月1日至2059年12月31日。

令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/日的,有采用月/日/年的,还有采用日/月/年的。

更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,存在很多可能的日期与其对应。

比如02/03/04,可能是2002年03月04日、2004年02月03日或2004年03月02日。

给出一个文献上的日期,你能帮助小明判断有哪些可能的日期对其对应吗?

输入格式

一个日期,格式是”AA/BB/CC”。

即每个’/’隔开的部分由两个 0-9 之间的数字(不一定相同)组成。

输出格式

输出若干个不相同的日期,每个日期一行,格式是”yyyy-MM-dd”。

多个日期按从早到晚排列。

数据范围

0≤A,B,C≤9

输入样例:
02/03/04
输出样例:
2002-03-04
2004-02-03
2004-03-02
难度:简单
时/空限制:1s / 64MB
总通过数:2465
总尝试数:7380
来源:第八届蓝桥杯省赛C++B组,第八届蓝桥杯省赛JAVAB组
算法标签模拟枚举
#include<bits/stdc++.h>
using namespace std;
int mon_day[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
bool isyear(int year){
    if(year%400==0||year%100!=0&&year%4==0)
        return true;
    return false;
}
int year,mon,day;
bool judge(int date){

    year=date/10000;
    mon=date%10000/100;
    day=date%100;
    if(day<1)
        return false;
    if(mon<1||mon>12)
        return false;
    if(mon!=2){
        if(day>mon_day[mon]){
            return false;
        }
    }
    else{
        if(isyear(year)){
            if(day>mon_day[2]+1)
                return false;
        }
        else if(day>mon_day[2])
            return false;
    }
    return true;
}
int main()
{
    int a,b,c;
    scanf("%d/%d/%d",&a,&b,&c);
    for(int i=19600101;i<=20591231;i++){
        if(judge(i)){
            //年/月/日的,有采用月/日/年的,还有采用日/月/年
            if((year%100==a&&mon==b&&day==c)||(mon==a&&day==b&&year%100==c)||(day==a&&mon==b&&year%100==c)){
                printf("%d-%02d-%02d\n",year,mon,day);
            }
        }
    }

    return 0;
}

1231. 航班时间

小 hh 前往美国参加了蓝桥杯国际赛。

小 hh 的女朋友发现小 hh 上午十点出发,上午十二点到达美国,于是感叹到“现在飞机飞得真快,两小时就能到美国了”。

小 hh 对超音速飞行感到十分恐惧。

仔细观察后发现飞机的起降时间都是当地时间。

由于北京和美国东部有 12小时时差,故飞机总共需要 14 小时的飞行时间。

不久后小 hh 的女朋友去中东交换。

小 hh 并不知道中东与北京的时差。

但是小 hh 得到了女朋友来回航班的起降时间。

小 hh 想知道女朋友的航班飞行时间是多少。

对于一个可能跨时区的航班,给定来回程的起降时间。

假设飞机来回飞行时间相同,求飞机的飞行时间。

输入格式

一个输入包含多组数据。

输入第一行为一个正整数 T,表示输入数据组数。

每组数据包含两行,第一行为去程的起降时间,第二行为回程的起降时间。

起降时间的格式如下:

  1. h1:m1:s1 h2:m2:s2
  2. h1:m1:s1 h3:m3:s3 (+1)
  3. h1:m1:s1 h4:m4:s4 (+2)

第一种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间当日h2时m2分s2秒降落。

第二种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间次日h2时m2分s2秒降落。

第三种格式表示该航班在当地时间h1时m1分s1秒起飞,在当地时间第三日h2时m2分s2秒降落。

输出格式

对于每一组数据输出一行一个时间hh:mm:ss,表示飞行时间为h小时m分s秒。

注意,当时间为一位数时,要补齐前导零,如三小时四分五秒应写为03:04:05。

数据范围

保证输入时间合法(0≤h≤23,0≤m,s≤59),飞行时间不超过24小时。

输入样例:
3
17:48:19 21:57:24
11:05:18 15:14:23
17:21:07 00:31:46 (+1)
23:02:41 16:13:20 (+1)
10:19:19 20:41:24
22:19:04 16:41:09 (+1)
输出样例:
04:09:05
12:10:39
14:22:05
难度:简单
时/空限制:1s / 64MB
总通过数:1711
总尝试数:2413
来源:第九届蓝桥杯省赛C++A组,第九届蓝桥杯省赛JAVAA组
算法标签模拟数学
#include<bits/stdc++.h>
using namespace std;

int get_seconds(int h, int m, int s)
{
    return h * 3600 + m * 60 + s;
}

int get_time()
{
    string line;
    getline(cin, line);

    if (line.back() != ')') line += " (+0)";

    int h1, m1, s1, h2, m2, s2, d;
    sscanf(line.c_str(), "%d:%d:%d %d:%d:%d (+%d)", &h1, &m1, &s1, &h2, &m2, &s2, &d);

    return get_seconds(h2, m2, s2) - get_seconds(h1, m1, s1) + d * 24 * 3600;
}

int main(){
	int n;
	cin>>n;
	string line;
    getline(cin, line); 
	while(n--){
		int time=(get_time()+get_time())/2;
		int hour = time / 3600, min = time % 3600 / 60, second = time % 60;
		printf("%02d:%02d:%02d\n",hour,min,second); 
	}
	return 0;
} 

1241. 外卖店优先级

“饱了么”外卖系统中维护着 N 家外卖店,编号 1∼N。

每家外卖店都有一个优先级,初始时 (0 时刻) 优先级都为 0。

每经过 1 个时间单位,如果外卖店没有订单,则优先级会减少 1,最低减到 0;而如果外卖店有订单,则优先级不减反加,每有一单优先级加 2。

如果某家外卖店某时刻优先级大于 5,则会被系统加入优先缓存中;如果优先级小于等于 3,则会被清除出优先缓存。

给定 T 时刻以内的 M 条订单信息,请你计算 T 时刻时有多少外卖店在优先缓存中。

输入格式

第一行包含 33 个整数 N,M,T。

以下 MM 行每行包含两个整数 ts 和 id,表示 ts 时刻编号 id 的外卖店收到一个订单。

输出格式

输出一个整数代表答案。

数据范围

1≤N,M,T≤105,
1≤ts≤T
1≤id≤N

输入样例:
2 6 6
1 1
5 2
3 1
6 2
2 1
6 2
输出样例:
1
样例解释

66 时刻时,11 号店优先级降到 33,被移除出优先缓存;22 号店优先级升到 66,加入优先缓存。

所以是有 11 家店 (22 号) 在优先缓存中。

难度:简单
时/空限制:1s / 64MB
总通过数:2092
总尝试数:8665
来源:第十届蓝桥杯省赛C++A/C组,第十届蓝桥杯省赛JAVAA/B/C组
算法标签模拟
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
int cnt[N];
struct message
{
    int t, id;
    bool operator < (const message& m)
    {
        return id < m.id || id == m.id && t < m.t;
    }
}m[N];
int main()
{
    int n, M, T;
    scanf("%d%d%d", &n, &M, &T);
    for(int i = 0; i < M; i++)  scanf("%d%d", &m[i].t, &m[i].id);
    sort(m, m + M);
    int cur = 2, ans = 0;
    bool flag = false;

    for(int i = 1; i <= M; i++)
    {
        if(m[i].id != m[i-1].id)
        {
            if(flag && cur - (T - m[i-1].t) > 3)    ans++;      //注意判断末订单时间
            flag = false;   //恢复初始值,为当前集合服务
            cur = 2;
        }
        else
        {
            int diff = m[i].t - m[i-1].t - 1;
            if(diff == -1)   diff = 0;
            cur = max(0, cur - diff);
            if(cur <= 3)    flag = false;
            cur += 2;
            if(cur > 5)     flag = true;
        }
    }
    printf("%d\n", ans);
    return 0;
}

第五讲 树状数组与线段树

这讲内容考的不多,当时直接没写。。。

第六讲 双指针、BFS与图论

1238. 日志统计

小明维护着一个程序员论坛。现在他收集了一份”点赞”日志,日志共有 N 行。

其中每一行的格式是:

ts id  

表示在 ts 时刻编号 id 的帖子收到一个”赞”。

现在小明想统计有哪些帖子曾经是”热帖”。

如果一个帖子曾在任意一个长度为 D 的时间段内收到不少于 K 个赞,小明就认为这个帖子曾是”热帖”。

具体来说,如果存在某个时刻 T 满足该帖在 [T,T+D) 这段时间内(注意是左闭右开区间)收到不少于 K 个赞,该帖就曾是”热帖”。

给定日志,请你帮助小明统计出所有曾是”热帖”的帖子编号。

输入格式

第一行包含三个整数 N,D,K

以下 N 行每行一条日志,包含两个整数 ts 和 id。

输出格式

按从小到大的顺序输出热帖 id。

每个 id占一行。

数据范围

1≤K≤N≤105
0≤ts,id≤105
1≤D≤10000

输入样例:
7 10 2
0 1
0 10
10 10
10 1
9 1
100 3
100 3
输出样例:
1
3
难度:中等
时/空限制:1s / 64MB
总通过数:2679
总尝试数:5410
来源:第九届蓝桥杯省赛C++B组,第九届蓝桥杯省赛JAVAB组
算法标签双指针滑动窗口
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 100010;

int n, d, k;
PII logs[N];
int cnt[N];
bool st[N];  // 记录每个帖子是否是热帖

int main()
{
    scanf("%d%d%d", &n, &d, &k);
    for (int i = 0; i < n; i ++ ) scanf("%d%d", &logs[i].x, &logs[i].y);

    sort(logs, logs + n);

    for (int i = 0, j = 0; i < n; i ++ )
    {
        int id = logs[i].y;
        cnt[id] ++ ;

        while (logs[i].x - logs[j].x >= d)
        {
            cnt[logs[j].y] -- ;
            j ++ ;
        }

        if (cnt[id] >= k) st[id] = true;
    }

    for (int i = 0; i <= 100000; i ++ )
        if (st[i])
            printf("%d\n", i);

    return 0;
}


1101. 献给阿尔吉侬的花束

阿尔吉侬是一只聪明又慵懒的小白鼠,它最擅长的就是走各种各样的迷宫。

今天它要挑战一个非常大的迷宫,研究员们为了鼓励阿尔吉侬尽快到达终点,就在终点放了一块阿尔吉侬最喜欢的奶酪。

现在研究员们想知道,如果阿尔吉侬足够聪明,它最少需要多少时间就能吃到奶酪。

迷宫用一个 R×C的字符矩阵来表示。

字符 S 表示阿尔吉侬所在的位置,字符 E 表示奶酪所在的位置,字符 # 表示墙壁,字符 . 表示可以通行。

阿尔吉侬在 1 个单位时间内可以从当前的位置走到它上下左右四个方向上的任意一个位置,但不能走出地图边界。

输入格式

第一行是一个正整数 T,表示一共有 T 组数据。

每一组数据的第一行包含了两个用空格分开的正整数 R 和 C,表示地图是一个 R×C 的矩阵。

接下来的 R 行描述了地图的具体内容,每一行包含了 C 个字符。字符含义如题目描述中所述。保证有且仅有一个 S 和 E。

输出格式

对于每一组数据,输出阿尔吉侬吃到奶酪的最少单位时间。

若阿尔吉侬无法吃到奶酪,则输出“oop!”(只输出引号里面的内容,不输出引号)。

每组数据的输出结果占一行。

数据范围

1<T≤10
2≤R,C≤200

输入样例:
3
3 4
.S..
###.
..E.
3 4
.S..
.E..
....
3 4
.S..
####
..E.
输出样例:
5
1
oop!
难度:简单
时/空限制:1s / 64MB
总通过数:4343
总尝试数:8344
来源:《信息学奥赛一本通》
算法标签BFS
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;

typedef pair<int,int> PII;

const int N = 210;
char g[N][N];
int vis[N][N];
int t,r,m;

int bfs(PII start, PII end)
{
    queue<PII> q;
    memset(vis, 0, sizeof vis);
    vis[start.x][start.y] = true;
    q.push(start);

    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
	int step=0;
    while (q.size())
    {
    	int ns=q.size();
    	for(int i=0;i<ns;i++){
    		auto t=q.front();
    		q.pop();
            for (int i = 0; i < 4; i ++ )
        	{
	            int x = t.x + dx[i], y = t.y + dy[i];
	            if (x < 0 || x >= r || y < 0 || y >= m) continue;  // 出界
	            if (g[x][y] == '#') continue;  // 障碍物
	            if (vis[x][y]) continue;  // 之前已经遍历过
	            if (end == make_pair(x, y)) return step+1;
	            vis[x][y]=1;
	            q.push({x, y});
        }
		}
		step++;
    }

    return -1;
}



int main(){
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&r,&m);
		for (int i = 0; i < r; i ++ ) scanf("%s", g[i]);
        PII start, end;
        for (int i = 0; i < r; i ++ )
            for (int j = 0; j < m; j ++ )
                if (g[i][j] == 'S') start = {i, j};
                else if (g[i][j] == 'E') end = {i, j};
        int distance = bfs(start, end);
        if (distance == -1) printf("oop!\n");
        else printf("%d\n", distance);
	}
	return 0;
} 
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <queue>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 210;

int n, m;
char g[N][N];
int dist[N][N];

int bfs(PII start, PII end)
{
    queue<PII> q;
    memset(dist, -1, sizeof dist);

    dist[start.x][start.y] = 0;
    q.push(start);

    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

    while (q.size())
    {
        auto t = q.front();
        q.pop();

        for (int i = 0; i < 4; i ++ )
        {
            int x = t.x + dx[i], y = t.y + dy[i];
            if (x < 0 || x >= n || y < 0 || y >= m) continue;  // 出界
            if (g[x][y] == '#') continue;  // 障碍物
            if (dist[x][y] != -1) continue;  // 之前已经遍历过

            dist[x][y] = dist[t.x][t.y] + 1;

            if (end == make_pair(x, y)) return dist[x][y];

            q.push({x, y});
        }
    }

    return -1;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        scanf("%d%d", &n, &m);
        for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);

        PII start, end;
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ )
                if (g[i][j] == 'S') start = {i, j};
                else if (g[i][j] == 'E') end = {i, j};

        int distance = bfs(start, end);
        if (distance == -1) puts("oop!");
        else printf("%d\n", distance);
    }

    return 0;
}

1224. 交换瓶子

有 N 个瓶子,编号 1∼N,放在架子上。

比如有 5 个瓶子:

2 1 3 5 4

要求每次拿起 2 个瓶子,交换它们的位置。

经过若干次后,使得瓶子的序号为:

1 2 3 4 5

对于这么简单的情况,显然,至少需要交换 2 次就可以复位。

如果瓶子更多呢?你可以通过编程来解决。

输入格式

第一行包含一个整数 N,表示瓶子数量。

第二行包含 N 个整数,表示瓶子目前的排列状况。

输出格式

输出一个正整数,表示至少交换多少次,才能完成排序。

数据范围

1≤N≤10000

输入样例1:
5
3 1 2 5 4
输出样例1:
3
输入样例2:
5
5 4 3 2 1
输出样例2:
2
难度:中等
时/空限制:1s / 64MB
总通过数:2314
总尝试数:3293
来源:第七届蓝桥杯省赛C++B组,第七届蓝桥杯省赛JAVAA组
算法标签图论置换群贪心
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n;
int s[10001];
int main() {
    int c = 0;
    scanf("%d",&n);
    for(int i = 1;i <= n;i ++) {
        scanf("%d",&s[i]);
    }
    for(int i = 1;i <= n;i ++) {
        while(s[i] != i) {
            c ++;
            //cout << s[i] << " " << s[s[i]] << endl; 
            swap(s[i],s[s[i]]);
            //for(int i = 1; i <= n; i ++) cout << s[i] << " ";
            //cout << endl;
        }
    }
    printf("%d",c);
}

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010;

int n;
int b[N];
bool st[N];

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

    int cnt = 0;
    for (int i = 1; i <= n; i ++ )
        if (!st[i])
        {
            cnt ++ ;
            for (int j = i; !st[j]; j = b[j])
                st[j] = true;
        }

    printf("%d\n", n - cnt);

    return 0;
}

#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 10010;

int n;
int b[N];
bool st[N];

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

    int cnt = 0;
    for (int i = 1; i <= n; i ++ )
        if (!st[i])
        {
            cnt ++ ;
            for (int j = i; !st[j]; j = b[j])
                st[j] = true;
        }

    printf("%d\n", n - cnt);

    return 0;
}
//置换群,找出环的个数,每交换一次可以多产生一个环,最终生成n个自环,所以找出k个环,最终的次数就是n-k

1113. 红与黑

有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。

你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。

请写一个程序,计算你总共能够到达多少块黑色的瓷砖。

输入格式

输入包括多个数据集合。

每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y 方向瓷砖的数量。

在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下

1)‘.’:黑色的瓷砖;
2)‘#’:红色的瓷砖;
3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。

当在一行中读入的是两个零时,表示输入结束。

输出格式

对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。

数据范围

1≤W,H≤20

输入样例:
6 9 
....#. 
.....# 
...... 
...... 
...... 
...... 
...... 
#@...# 
.#..#. 
0 0
输出样例:
45
//DFS
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 25;

int n, m;
char g[N][N];
bool st[N][N];

int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};

int dfs(int x, int y)
{
    int cnt = 1;

    st[x][y] = true;
    for (int i = 0; i < 4; i ++ )
    {
        int a = x + dx[i], b = y + dy[i];
        if (a < 0 || a >= n || b < 0 || b >= m) continue;
        if (g[a][b] != '.') continue;
        if (st[a][b]) continue;

        cnt += dfs(a, b);
    }

    return cnt;
}

int main()
{
    while (cin >> m >> n, n || m)
    {
        for (int i = 0; i < n; i ++ ) cin >> g[i];

        int x, y;
        for (int i = 0; i < n; i ++ )
            for (int j = 0; j < m; j ++ )
                if (g[i][j] == '@')
                {
                    x = i;
                    y = j;
                }

        memset(st, 0, sizeof st);
        cout << dfs(x, y) << endl;
    }

    return 0;
}
/*

//BFS

#include<iostream>
#include<cstring>
#include<queue>
#define x first
#define y second

using namespace std;
typedef pair<int,int> PII;

const int N=30;

char g[N][N];
int n,m;
int dx[4]={0,1,0,-1},dy[4]={1,0,-1,0};
bool st[N][N];

int bfs(int x,int y)
{
    int cnt=1;
    queue<PII> q;
    q.push({x,y});
    while(q.size())
    {
        PII t=q.front();
        q.pop();
        int x=t.x,y=t.y;
        for(int i=0;i<4;i++)
        {
            int a=x+dx[i],b=y+dy[i];
            if(a<0 || a>=n || b<0 || b>=m) continue;
            if(st[a][b]) continue;
            if(g[a][b]!='.') continue;
            st[a][b]=true;
            q.push({a,b});
            cnt++;
        }
    }
    return cnt;
}


int main()
{
    while(cin>>m>>n,n||m)
    {
        memset(st,0,sizeof st);
        for(int i=0;i<n;i++) scanf("%s",g[i]);
        int x,y,flag=0;
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
                if(g[i][j]=='@')
                {
                    x=i,y=j;
                    flag=1;
                }
            if(flag) break;
        }
        cout<< bfs(x,y) <<endl;
    }
    return 0;
}

*/

1240. 完全二叉树的权值

给定一棵包含 NN 个节点的完全二叉树,树上每个节点都有一个权值,按从上到下、从左到右的顺序依次是 A1,A2,⋅⋅⋅ANA1,A2,···AN,如下图所示:

QQ截图20191205124611.png

现在小明要把相同深度的节点的权值加在一起,他想知道哪个深度的节点权值之和最大?

如果有多个深度的权值和同为最大,请你输出其中最小的深度。

注:根的深度是 1。

输入格式

第一行包含一个整数 N。

第二行包含 N 个整数 A1,A2,⋅⋅⋅ANA。

输出格式

输出一个整数代表答案。

数据范围

1≤N≤105
−105≤Ai≤105

输入样例:
7
1 6 5 4 3 2 1
输出样例:
2
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 100010;

int n;
int a[N];

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

    LL maxs = -1e18;
    int depth = 0;

    for (int d = 1, i = 1; i <= n; i *= 2, d ++ )
    {
        LL s = 0;
        for (int j = i; j < i + (1 << d - 1) && j <= n; j ++ )
            s += a[j];

        if (s > maxs)
        {
            maxs = s;
            depth = d;
        }
    }

    printf("%d\n", depth);

    return 0;
}

1096. 地牢大师

你现在被困在一个三维地牢中,需要找到最快脱离的出路!

地牢由若干个单位立方体组成,其中部分不含岩石障碍可以直接通过,部分包含岩石障碍无法通过。

向北,向南,向东,向西,向上或向下移动一个单元距离均需要一分钟。

你不能沿对角线移动,迷宫边界都是坚硬的岩石,你不能走出边界范围。

请问,你有可能逃脱吗?

如果可以,需要多长时间?

输入格式

输入包含多组测试数据。

每组数据第一行包含三个整数 L,R,C 分别表示地牢层数,以及每一层地牢的行数和列数。

接下来是 L 个 R 行 C 列的字符矩阵,用来表示每一层地牢的具体状况。

每个字符用来描述一个地牢单元的具体状况。

其中, 充满岩石障碍的单元格用”#”表示,不含障碍的空单元格用”.”表示,你的起始位置用”S”表示,终点用”E”表示。

每一个字符矩阵后面都会包含一个空行。

当输入一行为”0 0 0”时,表示输入终止。

输出格式

每组数据输出一个结果,每个结果占一行。

如果能够逃脱地牢,则输出”Escaped in x minute(s).”,其中X为逃脱所需最短时间。

如果不能逃脱地牢,则输出”Trapped!”。

数据范围

1≤L,R,C≤100

输入样例:
3 4 5
S....
.###.
.##..
###.#

#####
#####
##.##
##...

#####
#####
#.###
####E

1 3 3
S##
#E#
###

0 0 0
输出样例:
Escaped in 11 minute(s).
Trapped!
难度:简单
时/空限制:1s / 64MB
总通过数:1785
总尝试数:3570
来源:《信息学奥赛一本通》 , POJ 2251
算法标签BFS
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 110;

struct Point
{
    int x, y, z;
};

int L, R, C;
char g[N][N][N];
Point q[N * N * N];
int dist[N][N][N];

int dx[6] = {1, -1, 0, 0, 0, 0};
int dy[6] = {0, 0, 1, -1, 0, 0};
int dz[6] = {0, 0, 0, 0, 1, -1};

int bfs(Point start, Point end)
{
    int hh = 0, tt = 0;
    q[0] = start;
    memset(dist, -1, sizeof dist);
    dist[start.x][start.y][start.z] = 0;

    while (hh <= tt)
    {
        auto t = q[hh ++ ];

        for (int i = 0; i < 6; i ++ )
        {
            int x = t.x + dx[i], y = t.y + dy[i], z = t.z + dz[i];
            if (x < 0 || x >= L || y < 0 || y >= R || z < 0 || z >= C) continue;  // 出界
            if (g[x][y][z] == '#') continue;  // 有障碍物
            if (dist[x][y][z] != -1) continue;  // 之前走到过

            dist[x][y][z] = dist[t.x][t.y][t.z] + 1;
            if (x == end.x && y == end.y && z == end.z) return dist[x][y][z];

            q[ ++ tt] = {x, y, z};
        }
    }

    return -1;
}

int main()
{
    while (scanf("%d%d%d", &L, &R, &C), L || R || C)
    {
        Point start, end;
        for (int i = 0; i < L; i ++ )
            for (int j = 0; j < R; j ++ )
            {
                scanf("%s", g[i][j]);
                for (int k = 0; k < C; k ++ )
                {
                    char c = g[i][j][k];
                    if (c == 'S') start = {i, j, k};
                    else if (c == 'E') end = {i, j, k};
                }
            }

        int distance = bfs(start, end);
        if (distance == -1) puts("Trapped!");
        else printf("Escaped in %d minute(s).\n", distance);
    }

    return 0;
}

1233. 全球变暖

你有一张某海域 N×N 像素的照片,”.”表示海洋、”#”表示陆地,如下所示:

.......
.##....
.##....
....##.
..####.
...###.
.......

其中”上下左右”四个方向上连在一起的一片陆地组成一座岛屿,例如上图就有 22 座岛屿。

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。

具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。

例如上图中的海域未来会变成如下样子:

.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。

输入格式

第一行包含一个整数N。

以下 N 行 N 列,包含一个由字符”#”和”.”构成的 N×N字符矩阵,代表一张海域照片,”#”表示陆地,”.”表示海洋。

照片保证第 1 行、第 1 列、第 N 行、第 N 列的像素都是海洋。

输出格式

一个整数表示答案。

数据范围

1≤N≤1000

输入样例1:
7
.......
.##....
.##....
....##.
..####.
...###.
.......
输出样例1:
1
输入样例2:
9
.........
.##.##...
.#####...
.##.##...
.........
.##.#....
.#.###...
.#..#....
.........
输出样例2:
1
难度:简单
时/空限制:1s / 64MB
总通过数:3080
总尝试数:6488
来源:第九届蓝桥杯省赛C++A/B组,第九届蓝桥杯省赛JAVAA/B组
算法标签[Flood Fill](https://www.acwing.com/problem/search/1/?search_content=Flood Fill)BFSDFS
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
typedef pair<int,int> PII;
const int N=1010;
char g[N][N];
bool vis[N][N];
bool flag,flag2;
int n,res,num,go;

void bfs(int a,int b){
	num++;
	flag2=false;
	vis[a][b]=true;
	queue<PII> q;
	q.push({a,b});
	int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};
	while(q.size()){
		PII t=q.front();
		q.pop();
		flag=true;
		for(int i=0;i<4;i++){
			int x=t.x+dx[i],y=t.y+dy[i];
			if(x<0||x>n||y<0||y>n) continue;
			if(vis[x][y]) continue;
			if(g[x][y]=='.') {
				flag=false;
				continue;
			}
			vis[x][y]=true;
			q.push({x,y});
		}
		if(flag&&!flag2) {
			go++;
			flag2=true;
		}
		
	}
}


int main(){
	cin>>n;
	for(int i=0;i<n;i++) cin>>g[i];
	
	for(int i=0;i<n;i++)
		for(int j=0;j<n;j++){
			if(g[i][j]=='#'&&!vis[i][j]) bfs(i,j);
		}
	cout<<num-go;
	return 0;
} 

1207. 大臣的旅费

很久以前,T王国空前繁荣。

为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。

同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J是T国重要大臣,他巡查于各大城市之间,体察民情。

所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。

他有一个钱袋,用于存放往来城市间的路费。

聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。

J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式

输入的第一行包含一个整数 n,表示包括首都在内的T王国的城市数。

城市从 1 开始依次编号,1 号城市为首都。

接下来 n−1 行,描述T国的高速路(T国的高速路一定是 n−1 条)。

每行三个整数 Pi,Qi,Di表示城市 Pi 和城市 Qi之间有一条双向高速路,长度为 Di 千米。

输出格式

输出一个整数,表示大臣J最多花费的路费是多少。

数据范围

1≤n≤105
1≤Pi,Qi≤n
1≤Di≤1000

输入样例:
5 
1  2  2 
1  3  1 
2  4  5 
2  5  4 
输出样例:
135
难度:中等
时/空限制:1s / 64MB
总通过数:2188
总尝试数:4520
来源:第四届蓝桥杯省赛C++A组,第四届蓝桥杯省赛JAVAA组
算法标签树的直径BFSDFS树形DP
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
struct Edge
{
    int id, w;
};
vector<Edge> h[N];
int dist[N];

void dfs(int u, int father, int distance)
{
    dist[u] = distance;

    for (auto node : h[u])
        if (node.id != father)
            dfs(node.id, u, distance + node.w);
}

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

    dfs(1, -1, 0);

    int u = 1;
    for (int i = 1; i <= n; i ++ )
        if (dist[i] > dist[u])
            u = i;

    dfs(u, -1, 0);

    for (int i = 1; i <= n; i ++ )
        if (dist[i] > dist[u])
            u = i;

    int s = dist[u];

    printf("%lld\n", s * 10 + s * (s + 1ll) / 2);//1ll表示将1转换为LL类型

    return 0;
}
/*
两遍dfs求最长图直径
*/

第七讲 贪心

本章当时未写,简单的贪心的话很容易看出来,难点的刷一点题用处不大,所以当时就没写了

第八讲 数论

本章也没咋写题,就看了算法笔记上的最大公因数和最小公倍数

1246. 等差数列

数学老师给小明出了一道等差数列求和的题目。

但是粗心的小明忘记了一部分的数列,只记得其中 N 个整数。

现在给出这 N 个整数,小明想知道包含这 N 个整数的最短的等差数列有几项?

输入格式

输入的第一行包含一个整数 N。

第二行包含 N 个整数 A1,A2,⋅⋅⋅,AN。(注意 A1∼AN并不一定是按等差数
列中的顺序给出)

输出格式

输出一个整数表示答案。

数据范围

2≤N≤100000
0≤Ai≤109

输入样例:
5
2 6 4 10 20
输出样例:
10
样例解释

包含 2、6、4、10、202、6、4、10、20 的最短的等差数列是 2、4、6、8、10、12、14、16、18、202、4、6、8、10、12、14、16、18、20。

难度:中等
时/空限制:1s / 64MB
总通过数:2604
总尝试数:7756
来源:第十届蓝桥杯省赛C++B/C组,第十届蓝桥杯省赛JAVAC组
算法标签数论最大公约数
#include<bits/stdc++.h>
using namespace std;

const int N=100010;
int q[N];

int gcd(int a,int b){
	return b? gcd(b,a%b):a;
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&q[i]);
	sort(q,q+n);
	int d=0;
	for(int i=1;i<n;i++){
		d=gcd(d,q[i]-q[0]);
	}
	if(!d) printf("%d\n",n);
	else printf("%d",(q[n-1]-q[0])/d+1);
	return 0;
} 

第九讲 复杂DP

这章居然也没写,现在发现当时写的题好少,不过当时dp还是在力扣上写过一些经典题的。。。我考的那次蓝桥杯再也不是暴力杯了,dp考的越来越多了。

.com/problem/content/description/1209/)

很久以前,T王国空前繁荣。

为了更好地管理国家,王国修建了大量的快速路,用于连接首都和王国内的各大城市。

为节省经费,T国的大臣们经过思考,制定了一套优秀的修建方案,使得任何一个大城市都能从首都直接或者通过其他大城市间接到达。

同时,如果不重复经过大城市,从首都到达每个大城市的方案都是唯一的。

J是T国重要大臣,他巡查于各大城市之间,体察民情。

所以,从一个城市马不停蹄地到另一个城市成了J最常做的事情。

他有一个钱袋,用于存放往来城市间的路费。

聪明的J发现,如果不在某个城市停下来修整,在连续行进过程中,他所花的路费与他已走过的距离有关,在走第x千米到第x+1千米这一千米中(x是整数),他花费的路费是x+10这么多。也就是说走1千米花费11,走2千米要花费23。

J大臣想知道:他从某一个城市出发,中间不休息,到达另一个城市,所有可能花费的路费中最多是多少呢?

输入格式

输入的第一行包含一个整数 n,表示包括首都在内的T王国的城市数。

城市从 1 开始依次编号,1 号城市为首都。

接下来 n−1 行,描述T国的高速路(T国的高速路一定是 n−1 条)。

每行三个整数 Pi,Qi,Di表示城市 Pi 和城市 Qi之间有一条双向高速路,长度为 Di 千米。

输出格式

输出一个整数,表示大臣J最多花费的路费是多少。

数据范围

1≤n≤105
1≤Pi,Qi≤n
1≤Di≤1000

输入样例:
5 
1  2  2 
1  3  1 
2  4  5 
2  5  4 
输出样例:
135
难度:中等
时/空限制:1s / 64MB
总通过数:2188
总尝试数:4520
来源:第四届蓝桥杯省赛C++A组,第四届蓝桥杯省赛JAVAA组
算法标签树的直径BFSDFS树形DP
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 100010;

int n;
struct Edge
{
    int id, w;
};
vector<Edge> h[N];
int dist[N];

void dfs(int u, int father, int distance)
{
    dist[u] = distance;

    for (auto node : h[u])
        if (node.id != father)
            dfs(node.id, u, distance + node.w);
}

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

    dfs(1, -1, 0);

    int u = 1;
    for (int i = 1; i <= n; i ++ )
        if (dist[i] > dist[u])
            u = i;

    dfs(u, -1, 0);

    for (int i = 1; i <= n; i ++ )
        if (dist[i] > dist[u])
            u = i;

    int s = dist[u];

    printf("%lld\n", s * 10 + s * (s + 1ll) / 2);//1ll表示将1转换为LL类型

    return 0;
}
/*
两遍dfs求最长图直径
*/

第七讲 贪心

本章当时未写,简单的贪心的话很容易看出来,难点的刷一点题用处不大,所以当时就没写了

第八讲 数论

本章也没咋写题,就看了算法笔记上的最大公因数和最小公倍数

1246. 等差数列

数学老师给小明出了一道等差数列求和的题目。

但是粗心的小明忘记了一部分的数列,只记得其中 N 个整数。

现在给出这 N 个整数,小明想知道包含这 N 个整数的最短的等差数列有几项?

输入格式

输入的第一行包含一个整数 N。

第二行包含 N 个整数 A1,A2,⋅⋅⋅,AN。(注意 A1∼AN并不一定是按等差数
列中的顺序给出)

输出格式

输出一个整数表示答案。

数据范围

2≤N≤100000
0≤Ai≤109

输入样例:
5
2 6 4 10 20
输出样例:
10
样例解释

包含 2、6、4、10、202、6、4、10、20 的最短的等差数列是 2、4、6、8、10、12、14、16、18、202、4、6、8、10、12、14、16、18、20。

难度:中等
时/空限制:1s / 64MB
总通过数:2604
总尝试数:7756
来源:第十届蓝桥杯省赛C++B/C组,第十届蓝桥杯省赛JAVAC组
算法标签数论最大公约数
#include<bits/stdc++.h>
using namespace std;

const int N=100010;
int q[N];

int gcd(int a,int b){
	return b? gcd(b,a%b):a;
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=0;i<n;i++) scanf("%d",&q[i]);
	sort(q,q+n);
	int d=0;
	for(int i=1;i<n;i++){
		d=gcd(d,q[i]-q[0]);
	}
	if(!d) printf("%d\n",n);
	else printf("%d",(q[n-1]-q[0])/d+1);
	return 0;
} 

第九讲 复杂DP

这章居然也没写,现在发现当时写的题好少,不过当时dp还是在力扣上写过一些经典题的。。。我考的那次蓝桥杯再也不是暴力杯了,dp考的越来越多了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值