[蓝桥杯][2018年第九届真题]小朋友崇拜圈、耐摔指数、堆的计数、缩位求和、约瑟夫环

个人题解链接,蓝桥杯历届试题,正在更新中~

小朋友崇拜圈

题目描述
班里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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值