个人题解链接,蓝桥杯历届试题,正在更新中~
小朋友崇拜圈
题目描述
班里N个小朋友,每个人都有自己最崇拜的一个小朋友(也可以是自己)。
在一个游戏中,需要小朋友坐一个圈,
每个小朋友都有自己最崇拜的小朋友在他的右手边。
求满足条件的圈最大多少人?
小朋友编号为1,2,3,…N
输入
输入第一行,一个整数N(3<N<100000)
接下来一行N个整数,由空格分开。
输出
要求输出一个整数,表示满足条件的最大圈的人数。
样例输入
9
3 4 2 5 3 8 4 6 9
样例输出
4
思路
这题做法好多,分享下我的。n个点n条边,允许自环,那么这种图的环一定是简单环,并且可能是混环,啥是混环,就相当于环上有一条边连出去了,我们先并查集处理每个集合的个数,然后把里面是混环上那条边的点去掉就可以。去掉混环上多出来的边可以用拓扑。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const double Pi = acos(-1);
namespace {
template <typename T> inline void read(T &x) {
x = 0; T f = 1;char s = getchar();
for(; !isdigit(s); s = getchar()) if(s == '-') f = -1;
for(; isdigit(s); s = getchar()) x = (x << 3) + (x << 1) + (s ^ 48);
x *= f;
}
}
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define _for(n,m,i) for (register int i = (n); i < (m); ++i)
#define _rep(n,m,i) for (register int i = (n); i <= (m); ++i)
#define _srep(n,m,i)for (register int i = (n); i >= (m); i--)
#define _sfor(n,m,i)for (register int i = (n); i > (m); i--)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define lowbit(x) x & (-x)
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e5+5;
int a[N], b[N], to[N], du[N];
int Find(int x) {
return x == a[x] ? x : a[x] = Find(a[x]);
}
void merge(int x, int y) {
int fx = Find(x), fy = Find(y);
if(fx != fy) a[fx] = fy;
}
queue<int> q;
int main() {
int n; read(n);
_rep(1, n, i) a[i] = i;
_rep(1, n, i) {
read(to[i]); merge(to[i], i);
du[to[i]]++;
}
_rep(1, n, i) {
b[Find(i)]++;
if(!du[i]) q.push(i);
}
while(!q.empty()) {
int u = q.front(); q.pop();
b[Find(u)]--;
du[to[u]]--;
if(!du[to[u]]) q.push(to[u]);
}
int Max = 0;
_rep(1, n, i) {
Max = max(Max, b[i]);
}
cout << Max << endl;
}
耐摔指数
题目描述
x星球的居民脾气不太好,但好在他们生气的时候唯一的异常举动是:摔手机。
各大厂商也就纷纷推出各种耐摔型手机。x星球的质监局规定了手机必须经过耐摔测试,并且评定出一个耐摔指数来,之后才允许上市流通。
x星球有很多高耸入云的高塔,刚好可以用来做耐摔测试。塔的每一层高度都是一样的,与地球上稍有不同的是,他们的第一层不是地面,而是相当于我们的2楼。
如果手机从第7层扔下去没摔坏,但第8层摔坏了,则手机耐摔指数=7。
特别地,如果手机从第1层扔下去就坏了,则耐摔指数=0。
如果到了塔的最高层第n层扔没摔坏,则耐摔指数=n
为了减少测试次数,从每个厂家抽样3部手机参加测试。
如果已知了测试塔的高度,并且采用最佳策略,在最坏的运气下最多需要测试多少次才能确定手机的耐摔指数呢?
输入
一个整数n(3<n<10000),表示测试塔的高度。
输出
输出一个整数,表示最多测试多少次。
样例输入
3
样例输出
2
思路
DP,dp[i][j] 代表i层,总共有j次机会的情况下,最多要试几次。转移方程 dp[i][j] = min(dp[i][j], max(dp[i-k][j]+1, dp[k-1][j-1]+1)), k=[1,i]。
感性的理解,若我们有j次机会,那么我们选择一个楼扔下去,如果碎了我们就还有j-1次机会,并且我们能判断出来只有前面的k-1层要测,正好可以从dp[i-k][j-1] 转移过来。如果没碎,我们就还有j次机会,只有后面的i-k层要测,这部分可以从dp[i-k][j] 转移过来。并且满足无后效性,每次用的状态都是已经确定的状态。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const double Pi = acos(-1);
namespace {
template <typename T> inline void read(T &x) {
x = 0; T f = 1;char s = getchar();
for(; !isdigit(s); s = getchar()) if(s == '-') f = -1;
for(; isdigit(s); s = getchar()) x = (x << 3) + (x << 1) + (s ^ 48);
x *= f;
}
}
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define _for(n,m,i) for (register int i = (n); i < (m); ++i)
#define _rep(n,m,i) for (register int i = (n); i <= (m); ++i)
#define _srep(n,m,i)for (register int i = (n); i >= (m); i--)
#define _sfor(n,m,i)for (register int i = (n); i > (m); i--)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define lowbit(x) x & (-x)
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e4+5;
int dp[4][N];
int main() {
int n; read(n);
memset(dp, 0x3f, sizeof dp);
_rep(0, n, i) dp[1][i] = i;
for(int i = 2; i <= 3; i++) {
dp[i][0] = 0;
for(int j = 1; j <= n; j++) {
for(int k = 1; k <= j; k++) {
dp[i][j] = min(dp[i][j], max(dp[i][j-k]+1, dp[i-1][k-1] + 1));
}
}
}
cout << dp[3][n] << endl;
}
堆的计数
题目描述
我们知道包含N个元素的堆可以看成是一棵包含N个节点的完全二叉树。
每个节点有一个权值。对于小根堆来说,父节点的权值一定小于其子节点的权值。
假设N个节点的权值分别是1~N,你能求出一共有多少种不同的小根堆吗?
例如对于N=4有如下3种:
1 / \ 2 3 / 4 1 / \ 3 2 / 4 1 / \ 2 4 / 3
由于数量可能超过整型范围,你只需要输出结果除以1000000009的余数。
输入
一个整数N。
输出
一个整数表示答案。
样例输入
4
样例输出
3
数据范围
对于 40% 的数据,1 <= N <= 1000
对于 70% 的数据,1 <= N <= 10000
对于 100% 的数据,1 <= N <= 100000
思路
dp,看到这个范围,肯定也是个线性dp。我们发现对于一个大小为n的完全二叉树,它的树形是可以确定的,并且它的左右子树也是完全二叉树,所以我们把它分为左子树ls,右子树rs。我们从n个数中选ls个给左儿子,这部分是没有限制的,直接就是一个组合数再乘上左右子树的方案。然后为了满足无后效性肯定是顺序转移。转移方程
d
p
[
i
]
=
d
p
[
l
s
]
∗
d
p
[
r
s
]
∗
C
(
i
,
l
s
)
dp[i] = dp[ls]* dp[rs] *C(i, ls)
dp[i]=dp[ls]∗dp[rs]∗C(i,ls) 计算组合数可以用线性递推逆元,然后再O(1)求解。可以先预处理出左子树的大小。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const double Pi = acos(-1);
namespace {
template <typename T> inline void read(T &x) {
x = 0; T f = 1;char s = getchar();
for(; !isdigit(s); s = getchar()) if(s == '-') f = -1;
for(; isdigit(s); s = getchar()) x = (x << 3) + (x << 1) + (s ^ 48);
x *= f;
}
}
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define _for(n,m,i) for (register int i = (n); i < (m); ++i)
#define _rep(n,m,i) for (register int i = (n); i <= (m); ++i)
#define _srep(n,m,i)for (register int i = (n); i >= (m); i--)
#define _sfor(n,m,i)for (register int i = (n); i > (m); i--)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define lowbit(x) x & (-x)
#define pii pair<int,int>
#define fi first
#define se second
const int N = 1e5+5;
const LL Mod = 1e9+9;
int rl[N];//n个节点的完全二叉树,以左儿子为根的子树有多少个节点
LL dp[N];
LL inv[N], fi[N];
LL ksm(LL a, LL b) {
LL ret = 1;
while(b) {
if(b & 1) ret = ret * a % Mod;
a = a * a % Mod;
b >>= 1;
}
return ret;
}
void init() {
fi[0] = 1;
_for(1, N, i) fi[i] = fi[i-1] * i % Mod;
inv[N-1] = ksm(fi[N-1], Mod-2);
_srep(N-2, 0, i) inv[i] = inv[i+1] * (i+1) % Mod;
}
LL C(int n, int m) {
if(n < m) return 0;
return fi[n] * inv[n-m] % Mod * inv[m] % Mod;
}
int main() {
init();
int n;read(n);
rl[2] = 1;
_rep(3, n, i) if(rl[i/2]) rl[i] = 1;
_rep(3, n, i) rl[i] += rl[i-1];
dp[0] = dp[1] = 1;
_rep(2, n, i) {
dp[i] = C(i-1, rl[i]) * dp[rl[i]] % Mod * dp[i-1-rl[i]] % Mod;
}
cout << dp[n] << endl;
}
缩位求和
题目描述
在电子计算机普及以前,人们经常用一个粗略的方法来验算四则运算是否正确。
比如:248 * 15 = 3720
把乘数和被乘数分别逐位求和,如果是多位数再逐位求和,直到是1位数,得
2 + 4 + 8 = 14 ==> 1 + 4 = 5;
1 + 5 = 6
5 * 6
而结果逐位求和为 3
5 * 6 的结果逐位求和与3符合,说明正确的可能性很大!!(不能排除错误)
请你写一个计算机程序,对给定的字符串逐位求和:
输入
输入为一个由数字组成的串,表示n位数(n<1000);
输出
输出为一位数,表示反复逐位求和的结果。
样例输入
35379
样例输出
9
思路
一道做烂了的题,可以套公式(n-1)%9+1,但是这里n很大,所以我们可以转化一下。(n%9+8)%9+1。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const double Pi = acos(-1);
namespace {
template <typename T> inline void read(T &x) {
x = 0; T f = 1;char s = getchar();
for(; !isdigit(s); s = getchar()) if(s == '-') f = -1;
for(; isdigit(s); s = getchar()) x = (x << 3) + (x << 1) + (s ^ 48);
x *= f;
}
}
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define _for(n,m,i) for (register int i = (n); i < (m); ++i)
#define _rep(n,m,i) for (register int i = (n); i <= (m); ++i)
#define _srep(n,m,i)for (register int i = (n); i >= (m); i--)
#define _sfor(n,m,i)for (register int i = (n); i > (m); i--)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define lowbit(x) x & (-x)
#define pii pair<int,int>
#define fi first
#define se second
char s[1005];
int main() {
scanf("%s", s);
int n = 0, len = strlen(s);
for(int i = len-1; i >= 0; i--) {
n = (n * 10 + s[i] -'0') % 9;
}
cout << (n + 8) % 9 + 1;
}
约瑟夫环
题目描述
n 个人的编号是 1~n,如果他们依编号按顺时针排成一个圆圈,从编号是1的人开始顺时针报数。
(报数是从1报起)当报到 k 的时候,这个人就退出游戏圈。下一个人重新从1开始报数。
求最后剩下的人的编号。这就是著名的约瑟夫环问题。
本题目就是已知 n,k 的情况下,求最后剩下的人的编号。
输入
题目的输入是一行,2个空格分开的整数n, k
输出
要求输出一个整数,表示最后剩下的人的编号。
样例输入
10 3
样例输出
4
思路
又是一道刷烂了的题,可以直接线性推,当然也有一个根号n的做法,用数论分块。证明过程网上有很多,就不说了。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 0x3f3f3f3f;
const double Pi = acos(-1);
namespace {
template <typename T> inline void read(T &x) {
x = 0; T f = 1;char s = getchar();
for(; !isdigit(s); s = getchar()) if(s == '-') f = -1;
for(; isdigit(s); s = getchar()) x = (x << 3) + (x << 1) + (s ^ 48);
x *= f;
}
}
#define fio ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define _for(n,m,i) for (register int i = (n); i < (m); ++i)
#define _rep(n,m,i) for (register int i = (n); i <= (m); ++i)
#define _srep(n,m,i)for (register int i = (n); i >= (m); i--)
#define _sfor(n,m,i)for (register int i = (n); i > (m); i--)
#define lson rt << 1, l, mid
#define rson rt << 1 | 1, mid + 1, r
#define lowbit(x) x & (-x)
#define pii pair<int,int>
#define fi first
#define se second
int main() {
int n, k; read(n); read(k);
int p = 0;
for(int i = 2; i <= n; i++) {
p = (p + k) % i;
}
cout << p+1 << endl;
}