蓝桥杯每日练习(并查集,状压dp,bfs)

蓝桥杯冲冲冲~

回顾
🔖蓝桥杯每日练习(神奇算式,缩位求和,积木大赛)

🔖蓝桥杯每日练习(猴子分香蕉,等差数列,平方序列,倍数问题)


🔖蓝桥杯每日练习(纯质数(筛法的应用),最少砝码,灌溉)


🔖蓝桥杯每日练习(年龄巧合,纸牌三角形,取球游戏)


🚶引言

今天继续蓝桥杯每日练习~ 今天的题目可以学到五个知识点:状态压缩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;
}
  • 8
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习算法的小菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值