🔔目录
蓝桥杯冲冲冲~
🔖蓝桥杯每日练习(猴子分香蕉,等差数列,平方序列,倍数问题)
🔖蓝桥杯每日练习(纯质数(筛法的应用),最少砝码,灌溉)
🔖蓝桥杯每日练习(年龄巧合,纸牌三角形,取球游戏)
🚶引言
今天继续蓝桥杯每日练习~ 今天的题目可以学到五个知识点:状态压缩dp,全排列,bfs,并查集,二分。
状压dp
有多重要应该不用我多说了,这是一种比较常用的dp,还有在高维dp题目通常都会有一维表示状态压缩。而且状压dp是很好的一种学习位运算的方式。并查集
是一种涉及到合并的很常用的数据结构,然后同时他因为还能维护很多的额外信息,所以也经常被用到。bfs
,二分
都是非常基本的算法,属于是必会了。我是学习算法的小菜鸡,每天练习几个知识点,我们国赛见~
🚀今日题目
💓带分数
全排列 |
题目描述
100 可以表示为带分数的形式:100 = 3 + 69258 / 714
还可以表示为:100 = 82 + 3546 / 197
注意特征:带分数中,数字 1~9 分别出现且只出现一次(不包含 0 )。
类似这样的带分数,100 有 11 种表示法。
程序输出该数字用数码 1~9 不重复不遗漏地组成带分数表示的全部种。
注意:不要求输出每个表示,只统计有多少表示法!
解题报告
直接对这九个数字进行全排列,然后枚举切割点来把他分成三个数字,然后判断是不是符合要求就行了。
参考代码(C++版本)
#include<iostream>
using namespace std;
const int N=15;
int st[N],res[N];
int idx,ans,n;
int f(int l,int r) {
int cnt=0;
for(int i=l;i<=r;i++) cnt=cnt*10+res[i];
return cnt;
}
void check() {
for(int i=1;i<=7;i++) {
for(int j=i+1;j<=8;j++) {
int a=f(1,i);
int b=f(i+1,j);
int c=f(j+1,9);
if (a*c+b==c*n) ans++;
}
}
}
void dfs(int k) {
if(k==9) {
check();
return;
}
for(int i=1;i<=9;i++) {
if(!st[i]) {
st[i]=1;res[++idx]=i;
dfs(k+1);
idx--;st[i]=0;
}
}
}
int main() {
cin>>n;
dfs(0);
cout<<ans<<endl;
}
🌟走迷宫
🌱题目描述
给定一个 N×M 的网格迷宫 G。G 的每个格子要么是道路,要么是障碍物(道路用 1 表示,障碍物用 0 表示)。
已知迷宫的入口位置为 (x1,y1) ,出口位置为 (x2 , y2) 。问从入口走到出口,最少要走多少个格子
原题传送门
bfs |
🌴解题报告
bfs模板题,利用bfs求出最短路即可。最短路问题通常用的是bfs,可以注意一下。
🌵参考代码(C++版本)
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
typedef pair<int, int> PII;
const int N = 110;
int dist[N][N], mp[N][N];
int n, m;
PII st, ed;
int dx[] = { -1,0,1,0 };
int dy[] = { 0,1,0,-1 };
void find() {
queue<PII>qu;
qu.push(st);
memset(dist, 0x3f, sizeof dist);
dist[st.first][st.second] = 0;
while (qu.size()) {
auto t = qu.front();
qu.pop();
for (int i = 0; i < 4; i++) {
int x = t.first + dx[i];
int y = t.second + dy[i];
if (x >= 0 && x < n && y >= 0 && y < m && mp[x][y]) {
mp[x][y] = 0;
dist[x][y] = min(dist[x][y], dist[t.first][t.second] + 1);
qu.push({ x,y });
}
}
}
}
int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> mp[i][j];
}
}
cin >> st.first >> st.second >> ed.first >> ed.second;
st.first--, st.second--, ed.first--, ed.second--;
find();
if (dist[ed.first][ed.second] == 0x3f3f3f3f) cout << -1 << endl;
else cout << dist[ed.first][ed.second];
}
蓝桥幼儿园
题目描述
蓝桥幼儿园的学生是如此的天真无邪,以至于对他们来说,朋友的朋友就是自己的朋友。
小明是蓝桥幼儿园的老师,这天他决定为学生们举办一个交友活动,活动规则如下:
小明会用红绳连接两名学生,被连中的两个学生将成为朋友。
小明想让所有学生都互相成为朋友,但是蓝桥幼儿园的学生实在太多了,他无法用肉眼判断某两个学生是否为朋友。于是他起来了作为编程大师的你,请你帮忙写程序判断某两个学生是否为朋友(默认自己和自己也是朋友)。
并查集 |
🚗解题报告
并查集的模板题。然后因为这道题太“板”了,所以不会并查集可以直接看代码学。
参考代码(C++版本)
#include<iostream>
using namespace std;
const int N = 2e5 + 10;
int p[N];
int n, m, op, a, b;
int find(int v) {
if (p[v] != v) p[v] = find(p[v]);
return p[v];
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) p[i] = i;
for (int i = 0; i < m; i++) {
cin >> op >> a >> b;
int pa = find(a), pb = find(b);
if (op == 1) {
p[pa] = pb;
}
else {
pa == pb ? cout << "YES" << endl : cout << "NO" << endl;
}
}
}
🙵跳石头
题目描述
一年一度的"跳石头"比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M 块岩石(不能移走起点和终点的岩石)。
同余与前缀和 |
🚇解题报告
这道题我们可以用二分答案的方式来解。答案是最小间隔长度的最大值,最值问题我们通常可以二分。最小答案是0,最大答案就是起点和终点的距离,那么二分的上下界我们便确定了。对于每一个数字,我们可以判断它是否能够作为答案,如果能,最终答案就只会大于等于当前答案,所以左边界等于这个数字,反之右边界等于这个数字减一(因为已经确定了这个数字不是答案,所以要减一)。判断函数怎么写呢?我们可以用一个flag来表示间隔长度大于检测值的能到达的点,然后遍历判断如果距离小就移除这块岩石,距离大就移动flag到这块岩石的位置,最后返回移除的岩石数是否小于等于最多移除数。
参考代码(C++版本)
#include<iostream>
using namespace std;
const int N = 5e4 + 10;
int q[N], st[N];
int n, m, k;
bool check(int v) {
int flag = 0, cnt = 0;
for (int i = 1; i <= m; i++) {
if (q[i] - flag < v) cnt++;
else flag = q[i];
}
return cnt <= k;
}
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= m; i++) cin >> q[i];
int l = 0, r = n;
while (l < r) {
int mid = l + r + 1>> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << r << endl;
}
👵回路计数
题目描述
蓝桥学院由 21 栋教学楼组成,教学楼编号 1 到 21。对于两栋教学楼 a 和 b,当 a 和 b 互质时,a 和 b 之间有一条走廊直接相连,两个方向皆可通行,否则没有直接连接的走廊。
小蓝现在在第一栋教学楼,他想要访问每栋教学楼正好一次,最终回到第一栋教学楼(即走一条哈密尔顿回路),请问他有多少种不同的访问方案?
两个访问方案不同是指存在某个 i,小蓝在两个访问方法中访问完教学楼 i 后访问了不同的教学楼。
gcd和状态压缩dp |
🖇解题报告
这道题也是一个很经典的状态压缩dp的应用。因为我们需要求欧拉路的数目,所以我们就需要记录一下那个点走过了那个点没走过,于是我们就想到了状态压缩。状态压缩是怎么回事呢?就是把每一个点的状态抽象到一个二进制数的一位上(就因为这个所以说状压dp题目一般数据不会很大),然后用位运算实现标记状态和查看状态。状压dp首先要二进制枚举状态,然后找到该状态下走过的点,然后再枚举一下这个点是从那个点走过来的就可以了。
参考代码(C++版本)
#include<iostream>
#define int long long
using namespace std;
const int N = 22, M = 1 << N;
int dist[N][N], num[M][N];
//dist[i][j]表示i,j之间有没有路,0表示没有路,1表示有路
//num[i][j]表示在i状态下最后到达j点的路径数
int gcd(int a, int b) {
if (a < b) return gcd(b, a);
if (b == 0) return a;
return gcd(b, a % b);
}
signed main() {
for (int i = 1; i <= 21; i++) {
for (int j = 1; j <= 21; j++) {
if (gcd(i, j) == 1) dist[i][j] = 1;
}
}
//用gcd标记一下那两个点之间有路
num[2][1] = 1;
//十进制的2就是二进制的10,表示其他点都不经过只经过第一个点的状态。初始化表示该状态下最后经过第一个点也就是起点的路径数有一条。
for (int i = 2; i < M - 1; i++) {
//枚举状态。为什么只枚举到M-2嘞?首先M-1才是所有位全是1的数字,然后再减一是因为没有第0个点
for (int j = 1; j <= 21; j++) {
//枚举每一个点
if (i >> j & 1) {
//如果这个状态经过这个点
int st = i - (1 << j); //因为不能重复走过某一点,所以在状态中把这个点的标记去除
for (int k = 1; k <= 21; k++) {
if (st >> k & 1) {
if (dist[k][j]) num[i][j] += num[st][k];
}
}
}
}
}
int ans = 0;
for (int i = 1; i <= 21; i++) ans += num[M - 2][i];
cout << ans << endl;
}