洛谷日常刷题(洛谷官方题单 思路+详解)

目录

前言

非官方题单的题

P1141 01迷宫

1-4 递推与递归

P1255 数楼梯

1002 【 NOIP 2002 普及组】过河卒

P1044 [NOIP2003 普及组] 栈

P1028 [NOIP2001 普及组] 数的计算

P1464 Function

P1928 外星密码


前言

经过 AcWing 算法基础课的熏陶,算法正式入门,在此记录洛谷刷题记录,此后计划,提高课学习 + 基础课复习 + 提高课看到哪一点,刷哪一点的题。

非官方题单的题

P1141 01迷宫

该题令我收益很大(兄弟们可以看看,对分析TLE情况很有用)

题目描述

有一个仅由数字0与1组成的n×n格迷宫。若你位于一格0上,那么你可以移动到相邻4格中的某一格1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。

输入格式

第1行为两个正整数n,m。

下面n行,每行n个字符,字符只可能是0或者1,字符之间没有空格。

接下来m行,每行2个用空格分隔的正整数i,j,对应了迷宫中第i行第j列的一个格子,询问从这一格开始能移动到多少格。

输出格式

m行,对于每个询问输出相应答案。

输入输出样例

输入 

2 2
01
10
1 1
2 2

输出 

4
4

说明/提示

所有格子互相可达。

对于20%的数据,n≤10;

对于40%的数据,n≤50;

对于50%的数据,m≤5;

对于60%的数据,n≤100,m≤100;

对于100%的数据,n≤1000,m≤100000。

思路:题目数据范围较大,直接bfs板子,TLE三个点,,

1>我们可以发现,在一次搜索中,搜索到的点数为路径上的点为一个集合中的点,换句话说,也就是本次搜索路径上的点的 答案,和本次搜索起点的 答案,是相同的!!!所以我们就不必重复搜索,大大减少工作量了,若 res[i][j] == 0,我们再搜索,不等于0,直接输出之前搜索的结果。

2>我们也不必在每次搜索时都 memset 标记数组一下;直接搜就可以,当res[i][j] == 0 时,是第一次搜到,那么应该正常的bfs;如果,当第二次搜到这个点时,证明这个本次搜索的起点,和这个点在同一条路径上,所以res[i][j] != 0,与之前的开始搜是的res[i][j] == 0 矛盾,所以不可能第二次搜到这个点!!!所以:一个点最多只会被搜一次

时间复杂度:O( max( n, m ) ),不知道能不能这样写,,有大佬的话帮忙分析一下(欢迎评论),

解释一下,考虑极限情况,一次把 n 全搜完了,这个的时间复杂度为O(n), 还剩m-1次,时间复杂度O(m),比较一下O(n)与O(m),取最大值,就是最终的时间复杂度。

代码如下

#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;
typedef pair PII;

const int N = 1010;

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

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

int bfs(int a, int b)
{
    queue q;
    vector d;
    q.push({a, b});
    st[a][b] = true;

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

        for(int i = 0; i < 4; i ++ )
        {
            int x = t.first + dx[i], y = t.second + dy[i];
            if(x >= 1 && x <= n && y >= 1 && y <= n && !st[x][y] && (g[x][y] ^ g[t.first][t.second]))
            {
                ans ++;
                d.push_back({x, y});
                st[x][y] = true;
                q.push({x, y});
            }
        }
    }

    for(auto t : d)
        res[t.first][t.second] = ans;

    return ans;
}

int main()
{
    scanf("%d%d", &n, &m);

    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= n; j ++ )
            cin >> g[i][j];

    while(m -- )
    {
        int a, b;
        scanf("%d %d", &a, &b);
        if( !res[a][b] ) res[a][b] = bfs(a, b);
        printf("%d\n", res[a][b]);
    }


    return 0;
}

1-4 递推与递归

P1255 数楼梯

题目描述

楼梯有 N 阶,上楼可以一步上一阶,也可以一步上二阶。

编一个程序,计算共有多少种不同的走法。

输入格式

一个数字,楼梯数。

输出格式

输出走的方式总数。

输入输出样例

输入

4

输出 

5

说明/提示

  • 对于 60% 的数据,N≤50;
  • 对于 100% 的数据,1≤N≤5000。

思路斐波那契数列(Fibonacci sequence),递推来解,需要注意的是,N <= 5000,结果是很大的一定能会超过 long long ,所以 高精度加法 用一下就好

代码如下

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int N = 2e5+10;

int n;

string add(string a, string b)
{
    if(a.size() < b.size()) return add(b, a);

    string c;
    int t = 0, cnt = 0;
    for(int i = 0; i < a.size(); i ++ )
    {
        t += a[i] - '0';
        if(i < b.size()) t += b[i] - '0';
        c += t % 10 + '0';
        t /= 10;
    }
    if(t) c += t + '0';

    return c;
}

int main()
{
    scanf("%d", &n);
    if(n == 1)
    {
        printf("1\n");
        return 0;
    }
    if(n == 2)
    {
        printf("2\n");
        return 0;
    }

    string a = "1", b = "2", c;
    for(int i = 3; i <= n; i ++ )
    {
        c = add(a, b);
        a = b;
        b = c;
    }

    for(int i = c.size() - 1; i >= 0; i -- )
        printf("%c", c[i]);

    return 0;
}


1002 【 NOIP 2002 普及组】过河卒

题目描述

棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

棋盘用坐标表示,A 点 (0,0)、B 点 (n,m),同样马的位置坐标是需要给出的。

洛谷日常刷题(洛谷官方题单 思路+详解)_第1张图片

现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入格式

一行四个正整数,分别表示 B 点坐标和马的坐标。

输出格式

一个整数,表示所有的路径条数。

输入输出样例

输入 

6 6 3 3

输出 

6

说明/提示

对于 100 \%100% 的数据,1 \le n, m \le 201≤n,m≤20,0 \le0≤ 马的坐标 \le 20≤20。

【题目来源】

NOIP 2002 普及组第四题

简单递归 == DP ???,可能就是这样吧hh。好了,言归正传,开始表演,,,

思路

1>,先初始化一下,马在的地方或马可以到达的地方卒不能走,设置为 true ,为不能走到的意思

2>,然后卒开始走,到达 [ i, j ] , 有两种方式,从上来,从左来,所以f [ i, j ] = f[ i-1, j ] + f[ i, j-1 ] 

3>,但需要注意的是,因为需要用到 f [ 0, 0 ] , f [ 0, y] , f[ x, 0] 等一系列点,这些点的值为 0 ,我们直接设为全局变量,同时将所有点向右下平移一步,所求值即为 f[ n, m ] ;

洛谷日常刷题(洛谷官方题单 思路+详解)_第2张图片

 代码如下:

#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 30;

int n, m, x, y;
LL f[N][N];
bool st[N][N];

void init()
{
    st[x][y] = true;
    st[x+2][y-1] = st[x+2][y+1] = true;
    st[x-2][y-1] = st[x-2][y+1] = true;
    st[x-1][y+2] = st[x+1][y+2] = true;
    st[x-1][y-2] = st[x+1][y-2] = true;
}

int main()
{
    scanf("%d%d%d%d", &n, &m, &x, &y);

    n ++, m ++, x ++, y ++;
    init();
    f[1][1] = 1;
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
        {
            if(i == 1 && j == 1) continue;
            if(!st[i][j]) f[i][j] = f[i-1][j] + f[i][j-1];
        }

    printf("%lld\n", f[n][m]);

    return 0;
}

P1044 [NOIP2003 普及组] 栈

题目背景

栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。

栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。

栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。

题目描述

洛谷日常刷题(洛谷官方题单 思路+详解)_第3张图片

宁宁考虑的是这样一个问题:一个操作数序列,1,2,…,n(图示为 1 到 3 的情况),栈 A 的深度大于 n。

现在可以进行两种操作,

  1. 将一个数,从操作数序列的头端移到栈的头端(对应数据结构栈的 push 操作)
  2. 将一个数,从栈的头端移到输出序列的尾端(对应数据结构栈的 pop 操作)

使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,下图所示为由 1 2 3 生成序列 2 3 1 的过程。

洛谷日常刷题(洛谷官方题单 思路+详解)_第4张图片

(原始状态如上图所示)

你的程序将对给定的 n,计算并输出由操作数序列 1,2,…,n 经过操作可能得到的输出序列的总数。

输入格式

输入文件只含一个整数 n(1≤n≤18)。

输出格式

输出文件只有一行,即可能输出序列的总数目。

输入输出样例

输入 

3

输出 

5

说明/提示

【题目来源】

NOIP 2003 普及组第三题

思路:卡特兰数板子题,输入为n时的输出的卡特兰数为,$$ C_{2n}^{n}/(n+1) $$

卡特兰数推导:

任意前缀中,push >= pop,即在绿色线下面,当遇到不合法数据时,该数据一定超过绿色线到达紫色线,然后再到达终点。我们将不合法的数据的线与紫色线的交点处以后的线,沿紫色线反转,则会到达上面的 [n-1, n+1] 点,所以合法的方案数为,全部的方案数 - 不合法的方案数,公式如下

 $$ C_{2n}^{n} - C_{2n}^{n-1} $$

化简可得:

$$ C_{2n}^{n}/(n+1) $$

证毕;

因为数据范围较小,求组合数可用递推式

 $$ C_{i}^{j} = C_{i-1}^{j} + C_{i-1}^{j-1} $$

洛谷日常刷题(洛谷官方题单 思路+详解)_第5张图片

 具体代码实现

#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;

const int N = 40;

int n;
LL c[N][N];

int main()
{
    scanf("%d", &n);

    for(int i = 0; i <= n * 2; i ++ )
        for(int j = 0; j <= i; j ++ )
            if(!j) c[i][j] = 1;
            else c[i][j] = c[i-1][j-1] + c[i-1][j];
    printf("%lld\n", c[2 * n][n]/(n+1));

    return 0;
}

P1028 [NOIP2001 普及组] 数的计算

题目描述

我们要求找出具有下列性质数的个数(包含输入的正整数 n)。

先输入一个正整数 n( n ≤ 1000 ),然后对此正整数按照如下方法进行处理:

  1. 不作任何处理;

  2. 在它的左边拼接一个正整数,但该正整数不能超过原数,或者是上一个被拼接的数的一半;

  3. 加上数后,继续按此规则进行处理,直到不能再加正整数为止。

输入格式

一行,一个正整数  n( n ≤ 1000 )。

输出格式

一个整数,表示具有该性质数的个数。

输入输出样例

输入 

6

输出 

6

说明/提示

【样例解释】

满足条件的数为:6,16,26,126,36,136。

【题目来源】

NOIP 2001 普及组第一题

思路:考察递推,也可以说是简单动态规划,看下图清晰明了

洛谷日常刷题(洛谷官方题单 思路+详解)_第6张图片

 代码如下:

#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;
typedef pair PII;

const int N = 1010;

int n, m;
int f[N];

int main()
{
    scanf("%d", &n);

    for(int i = 1; i <= n; i ++ )
    {
        f[i] ++;
        for(int j = 1; j <= i/2; j ++ )
            f[i] += f[j];
    }

    printf("%d\n", f[n]);

    return 0;
}

P1464 Function

题目描述

对于一个递归函数w(a,b,c)w(a,b,c)

  • 如果 a≤0 or b≤0 or c≤0就返回值 1.
  • 如果 a>20 or b>20 or c>20就返回 w(20,20,20)
  • 如果 a
  • 其它的情况就返回w(a−1,b,c)+w(a−1,b−1,c)+w(a−1,b,c−1)−w(a−1,b−1,c−1)

这是个简单的递归函数,但实现起来可能会有些问题。当 a,b,c 均为15时,调用的次数将非常的多。你要想个办法才行.

absi2011 : 比如 w(30,-1,0)w(30,−1,0)既满足条件1又满足条件2
这种时候我们就按最上面的条件来算
所以答案为1

输入格式

会有若干行。

并以 −1,−1,−1 结束。

保证输入的数在[−9223372036854775808,9223372036854775807]之间,并且是整数。

输出格式

输出若干行,每一行格式:

w(a, b, c) = ans

注意空格。

输入输出样例

输入 

1 1 1
2 2 2
-1 -1 -1

输出 

w(1, 1, 1) = 2
w(2, 2, 2) = 4

说明/提示

记忆化搜索

思路:直接调用函数,每次得到答案后保存在 res 数组中,若 res[a][b][c] == 0,则调用一下函数,否则直接输出 res[a][b][c] 

代码如下

#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef long long LL;
typedef pair PII;

const int N = 30;

LL n, m, k;
int res[N][N][N];

int w(int a, int b, int c)
{
    if(a <= 0 || b <= 0 || c <= 0) return 1;
    else if( res[a][b][c] != 0 ) return res[a][b][c];
    else if(a > 20 || b > 20 || c > 20) res[a][b][c] = w(20, 20, 20);
    else if(a < b && b < c) res[a][b][c] = w(a,b,c-1) + w(a,b-1,c-1) - w(a,b-1,c);
    else res[a][b][c] =w(a-1,b,c)+w(a-1,b-1,c)+w(a-1,b,c-1)-w(a-1,b-1,c-1);
    return res[a][b][c];

}

int main()
{
    while(scanf("%lld%lld%lld", &n, &m, &k))
    {
        if(n == -1 && m == -1 && k == -1) break;
        memset(res, 0, sizeof res);
        printf("w(%lld, %lld, %lld) = ", n, m, k);
        if(n > 20) n = 21;
        if(m > 20) m = 21;
        if(k > 20) k = 21;
        printf("%d\n",w(n, m, k));
    }

    return 0;
}

P1928 外星密码

题目描述

有了防护伞,并不能完全避免 2012 的灾难。地球防卫小队决定去求助外星种族的帮 助。经过很长时间的努力,小队终于收到了外星生命的回信。但是外星人发过来的却是一 串密码。只有解开密码,才能知道外星人给的准确回复。解开密码的第一道工序就是解压 缩密码,外星人对于连续的若干个相同的子串“X”会压缩为“[DX]”的形式(D 是一个整 数且 1≤D≤99),比如说字符串“CBCBCBCB”就压缩为“[4CB]”或者“[2[2CB]]”,类 似于后面这种压缩之后再压缩的称为二重压缩。如果是“[2[2[2CB]]]”则是三重的。现 在我们给你外星人发送的密码,请你对其进行解压缩。

输入格式

第一行:一个字符串

输出格式

第一行:一个字符串

输入输出样例

输入 

AC[3FUN]

输出 

ACFUNFUNFUN

说明/提示

【数据范围】

对于 50%的数据:解压后的字符串长度在 1000 以内,最多只有三重压缩。

对于 100%的数据:解压后的字符串长度在 20000 以内,最多只有十重压缩。 对于 100%的数据:保证只包含数字、大写字母、’[‘和’]‘

思路:碰见 ' [ '  保存 n 一下,进 dfs 一层,遇见 ' ] ',退出一层,此时,在上一层的while循环的里面,最终字符串 += 刚刚出来的字符串,到达最后一次 ' ] ' ,返回到主函数,得到结果

代码如下

#include 

using namespace std;

typedef long long LL;
typedef pair PII;

const int N = 30;

int n, m, k;
string str;
char c;

string dfs()
{
    string s, ss;
    int n;
    while(cin >> c)
    {
        if(c == '[')
        {
            cin >> n;
            ss = dfs();
            while(n -- ) s += ss;
        }
        else
        {
            if(c == ']') return s;
            else s += c;
        }
    }
}

int main()
{
    cin >> str;

    cout << dfs() << endl;

    return 0;
}

P2437 蜜蜂路线

与斐波那契数列一样,即和第一题数楼梯一样,详解看走楼梯

P1164 小A点菜

题目背景

uim神犇拿到了uoira(镭牌)后,立刻拉着基友小A到了一家……餐馆,很低端的那种。

uim指着墙上的价目表(太低级了没有菜单),说:“随便点”。

题目描述

不过uim由于买了一些书,口袋里只剩M元(M≤10000)。

餐馆虽低端,但是菜品种类不少,有N种(N≤100),第i种卖ai​元(ai​≤1000)。由于是很低端的餐馆,所以每种菜只有一份。

小A奉行“不把钱吃光不罢休”,所以他点单一定刚好把uim身上所有钱花完。他想知道有多少种点菜方法。

由于小A肚子太饿,所以最多只能等待1秒。

输入格式

第一行是两个数字,表示N和M。

第二行起N个正数ai​(可以有相同的数字,每个数字均在1000以内)。

输出格式

一个正整数,表示点菜方案数,保证答案的范围在int之内。

输入输出样例

输入 

4 4
1 1 2 2

输出

3

思路: 

若当前的钱足够,则状态转移为 不选 与 选当前的物品的办法的集合;若钱不足,则等于不选当前物品的办法的集合

洛谷日常刷题(洛谷官方题单 思路+详解)_第7张图片

 代码如下

#include 
#include 
#include 
#include 
#include 

using namespace std;

typedef pair PII;

const int N = 150, M = 10010;

int n, m;
int a[N];
int f[N][M];

int main()
{
    scanf("%d%d", &n, &m);

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

    for(int i = 1; i <= n; i ++ )
    {
        for(int j = 1; j <= m; j ++ )
        {
            if(j == a[i]) f[i][j] = f[i-1][j]+1;
            if(j > a[i]) f[i][j] = f[i-1][j] + f[i-1][j-a[i]];
            if(j < a[i]) f[i][j] = f[i-1][j];
        }
    }

    printf("%d\n", f[n][m]);

    return 0;
}

你可能感兴趣的:(算法,c++,蓝桥杯,数据结构)