目录
前言
非官方题单的题
P1141 01迷宫
1-4 递推与递归
P1255 数楼梯
1002 【 NOIP 2002 普及组】过河卒
P1044 [NOIP2003 普及组] 栈
P1028 [NOIP2001 普及组] 数的计算
P1464 Function
P1928 外星密码
经过 AcWing 算法基础课的熏陶,算法正式入门,在此记录洛谷刷题记录,此后计划,提高课学习 + 基础课复习 + 提高课看到哪一点,刷哪一点的题。
该题令我收益很大(兄弟们可以看看,对分析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;
}
题目描述
楼梯有 N 阶,上楼可以一步上一阶,也可以一步上二阶。
编一个程序,计算共有多少种不同的走法。
输入格式
一个数字,楼梯数。
输出格式
输出走的方式总数。
输入输出样例
输入
4
输出
5
说明/提示
思路:斐波那契数列(Fibonacci sequence),递推来解,需要注意的是,N <= 5000,结果是很大的一定能会超过 long long ,所以 高精度加法 用一下就好
代码如下
#include
#include
#include
#include
#include
题目描述
棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,A 点 (0,0)、B 点 (n,m),同样马的位置坐标是需要给出的。
现在要求你计算出卒从 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 ] ;
代码如下:
#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;
}
题目背景
栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表。
栈有两种最重要的操作,即 pop(从栈顶弹出一个元素)和 push(将一个元素进栈)。
栈的重要性不言自明,任何一门数据结构的课程都会介绍栈。宁宁同学在复习栈的基本概念时,想到了一个书上没有讲过的问题,而他自己无法给出答案,所以需要你的帮忙。
题目描述
宁宁考虑的是这样一个问题:一个操作数序列,1,2,…,n(图示为 1 到 3 的情况),栈 A 的深度大于 n。
现在可以进行两种操作,
使用这两种操作,由一个操作数序列就可以得到一系列的输出序列,下图所示为由 1 2 3
生成序列 2 3 1
的过程。
(原始状态如上图所示)
你的程序将对给定的 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} $$
具体代码实现:
#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;
}
题目描述
我们要求找出具有下列性质数的个数(包含输入的正整数 n)。
先输入一个正整数 n( n ≤ 1000 ),然后对此正整数按照如下方法进行处理:
不作任何处理;
在它的左边拼接一个正整数,但该正整数不能超过原数,或者是上一个被拼接的数的一半;
加上数后,继续按此规则进行处理,直到不能再加正整数为止。
输入格式
一行,一个正整数 n( n ≤ 1000 )。
输出格式
一个整数,表示具有该性质数的个数。
输入输出样例
输入
6
输出
6
说明/提示
【样例解释】
满足条件的数为:6,16,26,126,36,136。
【题目来源】
NOIP 2001 普及组第一题
思路:考察递推,也可以说是简单动态规划,看下图清晰明了
代码如下:
#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;
}
题目描述
对于一个递归函数w(a,b,c)w(a,b,c)
这是个简单的递归函数,但实现起来可能会有些问题。当 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;
}
题目描述
有了防护伞,并不能完全避免 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;
}
与斐波那契数列一样,即和第一题数楼梯一样,详解看走楼梯
题目背景
uim
神犇拿到了uoi
的ra
(镭牌)后,立刻拉着基友小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
思路:
若当前的钱足够,则状态转移为 不选 与 选当前的物品的办法的集合;若钱不足,则等于不选当前物品的办法的集合
代码如下:
#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;
}