牛客小白月赛94
牛客小白月赛94 (3/6)
A
题目描述
在一些安全性要求较高的APP中,通常我们输入密码时,系统弹出的输入框都是乱序的。这样一来就能防止想通过观察手指点击位置来推测密码的坏人。
现在小苯有一个可能乱序的九宫格按键,但他没注意到九宫格是乱序,因此他还是按照正常九宫格顺序点击的按键。
(正常九宫格:也就是按照 \(1\) 到 \(9\) 分为三行三列,从上到下,从左到右都是递增的,下方备注有图)
请你告诉他,在他点击完按键后,屏幕上显示的数字都应该是什么?
输入描述
输入包含四行。
第一到三行,每行三个正整数以空格分割,表示题目所述的“九宫格”按键。(保证输入是一个合法的九宫格,即 \(1\) 到 \(9\) 每个数字都恰好出现一次。)
第四行一个数字串 \(s\ (1 \leq |s| \leq 100, 1 \leq s_i \leq 9)\),表示小苯会按照正常九宫格顺序输入的数字串。(其中 \(|s|\) 表示 \(s\) 的长度。)
输出描述
输出一行一个数字串,表示小苯输入后,屏幕上的结果字符串。
(输出仅包含数字,数字间无需用空格间隔)
示例
输入
2 9 4
3 5 7
6 1 8
123456987
输出
294357816
说明
如下为样例的九宫格按键,在正常九宫格按键下按照“123456987”的顺序键入,显然结果应该是“294357816”。
题解
映射 + 模拟
注意输入的数字串是以字符串形式输入的,要将ACSCII码转换为整型
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int g[12];
string s;
void solve()
{
for(int i = 1; i < 10; i ++ )
cin >> g[i];
cin >> s;
for (int i = 0; i < s.size(); i ++ )
{
cout << g[s[i] - '0']; //这里是唯一的注意点
}
cout << endl;
return;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
B
题目描述
大白熊给了小苯一个长度为 \(n\) 的数组 \(a\),这次他希望小苯从数组中选择一个子序列(下方备注有定义解释),满足这个子序列构成的数组是一个“好数组”。
大白熊定义好数组是:如果一个数组按升序排序后和原来不完全相同,则其是一个好数组。例如 \([3,2,2]\) 升序排序后是 \([2, 2, 3]\),和原来不完全相同,因此是一个好数组,而 \([1,2,2]\) 不是一个好数组。
小苯想知道,如果想要使得选择的子序列构成一个“好数组”,最长可以选多长的子序列?
输入描述
输入包含两行。
第一行一个正整数 \(n \ (1 \leq n \leq 2 \times 10^5)\),表示数组 \(a\) 的长度。
第二行 \(n\) 的正整数 \(a_i (1\leq a_i \leq 10^9)\),表示数组 \(a\) 的元素。
输出描述
输出包含一行一个整数,表示可以构成“好数组”的最长子序列的长度。
示例
输入
1
1
输出
0
说明
只能选择 \(1\),但 \([1]\) 这个数组满足单调不降,因此无法选择数字,答案为 \(0\)。
题解
模拟样例 推导结论
只有两种情况
- 数组原本就是升序排列的,此时其子序列也是升序排列的,选不出一个“好数组”。
- 数组不是升序排列的,则其升序排列后与原数组不同,整个数组都是“好数组”,长度为n。
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
int n;
int a[N], b[N];
void solve()
{
cin >> n;
for (int i = 0; i < n; i ++ )
{
cin >> a[i];
b[i] = a[i];
}
sort(a, a + n);
bool flag = true;
for (int i = 0; i < n; i ++ )
{
if (a[i] != b[i])
{
flag = false;
break;
}
}
if (!flag) cout << n << endl;
else cout << 0 << endl;
return;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
C
题目描述
大白熊给了小苯一个长度为 \(n\) 的数组 \(a\),小苯想要最大化 \(a\) 的极差。
具体的,小苯可以做如下操作任意次(前提是数组至少有两个数字):
\(\bullet\) 选择一个正整数 \(i \ (1 \leq i <n)\),接着将 \(a_i\) 与 \(a_{i+1}\) 合并为一个数字,结果为二者的和。
(即:将 \(a_i\) 变为 \(a_i + a_{i+1}\),然后删去 \(a_{i+1}\),当然操作完后 \(a\) 的长度也会减一。)
小苯想知道他最大能将数组极差变为多少呢,请你帮帮他吧。
输入描述
输入包含两行。
第一行一个正整数 \(n\ (1 \leq n \leq 2 \times 10^5)\),表示数组 \(a\) 的长度。
第二行 \(n\) 个正整数 \(a_i\ (1 \leq a_i \leq 10^9)\),表示初始时数组 \(a\) 的元素。
输出描述
输出包含一行一个整数,表示小苯操作完后,数组 \(a\) 的最大极差。
示例
输入
4
3 2 2 3
输出
4
说明
一种可能的操作方式是:
选择将 \(a_2, a_3\) 合并,数组此时变为\([3, 4, 3]\)。
然后再选择 \(a_2, a_3\) 合并,数组此时变为 \([3, 7]\),极差为 \(4\)。
可以证明不存在比 \(4\) 更大的极差。
数组的极差定义为:数组中的最大值和最小值的差。
题解
贪心
这个题关键点是我们只能将相邻两数合并,那我们就让一个进行了多次相邻合并的大数和一个没合并的小数求极差,就能找到数组的最大极差。
怎么进行相邻合并呢?利用前缀和。
例如对于数组[3, 4, 2, 1, 3],如果将2看作最小数,就需要取出 max( 从数组左端将(3, 4)合并后与2求极差的值, 从数组右端将(1, 3)合并后与2求极差的值),也就是说我们需要从数组两端求前缀和,也就是数组的前缀和 和 后缀和。
我们遍历数组里的每个数将其看作最小数,求出其前缀和这个数的极差 和 其后缀和这个数的极差,遍历完后就能找到数组内的最大极差
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
int n, res;
int a[N], sa[N], b[N], sb[N];
void solve()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1, j = n; i <= n; i ++ ) b[j -- ] = a[i]; //数组b[]是数组a[]的倒序,用于求后缀和
for (int i = 1; i <= n; i ++ ) sa[i] = sa[i - 1] + a[i]; //数组a[]前缀和
for (int i = 1; i <= n; i ++ ) sb[i] = sb[i - 1] + b[i]; //数组b[]前缀和,也就是数组a[]的后缀和
//cout << sa[n];
for (int i = 1; i <= n; i ++ )
{
res = max(res, sa[i - 1] - a[i]); //求第i个数的前缀与其的极差,再与之前保留的极差作比较,取出最大值
res = max(res, sb[i - 1] - b[i]); //求第i个数的后缀与其的极差,再与之前保留的极差作比较,取出最大值
}
cout << res << endl;
return;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
D
题目描述
格格有一个长度为 \(n\) 的排列 \(p\),但她不记得 \(p\) 具体的样子,她只记得数组 \(a\)。
其中:\(a_i = gcd(p_1, p_2,...,p_i)\),也就是说,\(a_i\) 表示排列 \(p\) 中前 \(i\) 个数字的最大公约数。
现在,她希望小苯将排列 \(p\) 复原出来,请你帮帮他吧。
(但有可能无解,这意味着格格给出的 \(a\) 数组可能是不正确的,此时输出 \(-1\) 即可。)
输入描述
输入包含两行。
第一行一个正整数 \(n\ (1 \leq n \leq 2 \times 10^5)\),表示数组 \(a\) 的长度。
第二行 \(n\) 个正整数 \(a_i\ (1 \leq a_i \leq n)\),表示数组 \(a\) 的元素。
输出描述
输出包含一行 \(n\) 个正整数,表示符合条件的排列 \(p\)。
如果有多个解,输出任意方案即可。
如果无解,请输出一个数字:\(-1\)。
示例
输入
4
4 2 1 1
输出
4 2 1 3
说明
首先输出是一个排列,且满足格格的要求:
\(a_1 = gcd(p_1) = 4\)
\(a_2 = gcd(p_1, p_2) = 2\)
\(a_3 = gcd(p_1, p_2, p_3) = 1\)
\(a_4 = gcd(p_1, p_2, p_3, p_4) = 1\)
题解
限制条件
-
相邻的两个a[]中后一个必须是前一个的因数,必须满足a[i-1] % a[i] = 0,否则无法构造
证明 (ans[i]是最终构造出的数列)
\(a[i] = gcd(ans[1],...ans[i])\)
\(a[i-1] = gcd(ans[1],...ans[i-1])\)
\(∴ a[i] = gcd(a[i-1],ans[i])\)
\(∴ a[i-1]\mod a[i]=0,ans[i]\mod a[i]=0\)由 a[i-1] % a[i] = 0可知,a是一个非增序列, a[i - 1]与a[i]只有两种情况, a[i - 1] == a[i], a[i - 1] > a[i]
-
当 a[i - 1] > a[i]
由于我们推导出的a[]是递减数列(不一定单调),意味着此时的 a[i] 是第一次出现, 那么数a[i]一定是没使用过的, 该位可以直接填a[i],因为a[i] = gcd(a[i - 1], a[i])是一定成立的,满足我们需要构造的数的条件
-
当 a[i - 1] == a[i]
那么需要找一个a[i]的未使用倍数
填多少倍:
与1的互换同理, 找一个最小的满足条件的倍数即可从哪开始找:
不能直接从a[i]开始查找, 会超时
∵ a[i] = gcd(ans[1],...ans[i]) gcd()里的每一个数都是a[i]的倍数
∴ ans[i]是a[i]的倍数, ans[i-1]也是a[i]的倍数, 且 ans[i] > ans[i-1]
所以可以从ans[i-1]+a[i]开始查找不能从a[i] + a[i] 开始找,如果a[]数组元素全部相同,由于查找上限是n,很容易运行超时
记住每一次取出一个数放入ans[i],就要记录该数为已使用
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
int n, a[N], ans[N];
bool used[N];
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
int find(int i)
{
//查找a[i]的倍数
// ans[i] > ans[i-1], ans[i-1]也是a[i]的倍数
for (int f = ans[i - 1] + a[i]; f <= n; f += a[i])
{
if (!used[f] && gcd(a[i - 1], f) == a[i])
{
used[f] = true;
return f;
}
}
return -1;
}
void solve()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> a[i];
for (int i = 1; i < n; i ++)
{
if(a[i - 1] % a[i] != 0) //初筛数组 不满足限制条件直接return
{
cout << -1 << endl;
return;
}
}
ans[0] = a[0]; //第一个数直接设为a[0]
used[a[0]] = true ;
for (int i = 1; i < n; i ++ )
{
if (a[i - 1] != a[i])
{
ans[i] = a[i];
used[ans[i]] = true;
}
else
ans[i] = find(i);
//cout << ans[i] << endl;
if (ans[i] == -1) //如果在find()中查找失败 return
{
cout << -1 << endl;
return;
}
}
for (int i = 0; i < n; i ++ ) cout << ans[i] << " ";
cout << endl;
return;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
E
题目描述
小苯有一个容量为 \(k\) 的背包,现在有 \(n\) 个物品,每个物品有一个体积 \(v\) 和价值 \(w\),他想知道在体积不超过 \(k\) 的前提下,他最多能装价值为多少的物品。
本问题中,物品的总体积定义为所装物品的体积的 \(\&\)(按位与),总价值也定义为所装物品的价值的 \(\&\)(按位与)。
(如果不选物品,则价值为 0,所占体积也为 0。)
输入描述
输入包含 \(n+1\) 行。
第一行两个正整数 \(n, k\ (1\leq n\leq 2\times10^3, 0 \leq k \leq 2 \times 10^3)\),分别表示物品个数和背包容量。
加下来 \(n\) 行,每行两个正整数 \(v_i, w_i\ (0 \leq v_i, w_i \leq 2 \times 10^3)\),表示每个物品的体积和价值。
输出描述
输出包含一行一个整数,表示能装的最大价值。
示例
输入
3 1
7 3
10 7
9 6
输出
2
说明
选择第一个和第三个物品。
体积为:\(7\ \& \ 9 = 1\)。
价值为:\(3\ \&\ 6 = 2\)。可以证明不存在比 \(2\) 更大的价值。```
题解
位运算经典做法 + 暴力
与运算, 选的越多, 价值越低, 但总体积也越小越可行,所以我们选择的物品数量在满足条件的情况下越多越好,这样才可以将体积降下来
现在需要找到最大价值, 设其为ans
∴ value[1] & value[2] & ... = ans 将所有选择的物品价值 &后就是答案
∴ ans & value[i] = ans (ans在任意一位的1, 每个value在对应位上都有) 核心 利用该条件判断当前物品是否是组成value[i]的一部分
假设ans = 10110
那么只要第1位、第3位、第4位都为1, 那么这个物品对于这个答案来说就是可选的
而体积是选的越多越可行, 所以可以枚举答案
然后按"ans & value[i] = ans"这个条件,选出所有物品
计算出这些物品的总体积, 如果满足容量k限制, 则找到一个可行解
对于E, 数据范围2e3, 可以直接从2000开始枚举答案,最多枚举2000 * 2000 次(枚举所有可能的价值时枚举所有物品),不到\(10^7\)。
由于单个物品最大价值为2000,进行&运算后的价值肯定是 <= 2000,所以我们就从2000开始枚举最后得到的最大价值,如果最后满足题目给的背包容积k,那就是合法答案,最后留下满足容积k的最大合法答案
&
运算优先级低于==
所以表达式一定要加小括号,避免出错。
代码实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e3 + 10;
int n, k, ans;
int v[N], w[N];
void solve()
{
cin >> n >> k;
for (int i = 0; i < n; i ++ ) cin >> v[i] >> w[i];
for (int i = 2000; i >= 0; i -- ) //从2000开始枚举答案,确保如果枚举成功先枚举到最大价值
{
int v_sum = (1 << 30) - 1; //初始化装满最大价值i所需容积 保证有29位1,方便找到第一个合法物品时进行&运算后直接变成该物品的体积
for (int j = 0; j < n; j ++ )
{
if ((w[j] & i) == i) //核心代码 如果当前枚举到的物品满足枚举到价值i的要求 ** (w[j] & i)不加这个括号就WA了,&运算优先级低于==
{
v_sum &= v[j];
}
}
if (v_sum <= k) //如果满足题目要求的背包容积 直接输出此时的价值 因为我们从2000开始向下查找 先找到满足条件的i肯定是最大的
{
cout << i << endl;
return;
}
}
cout << 0 << endl; //进行到这里表示没有找到满足条件的i,则不选物品,价值为 0,所占体积也为 0
return;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}
F
与E题只有数据范围不同
输入描述
输入包含 \(n+1\) 行。
第一行两个正整数 \(n, k\ (1\leq n \leq 2\times10^5, 0 \leq k \leq 10^9)\),分别表示物品个数和背包容量。
加下来 \(n\) 行,每行两个正整数 \(v_i, w_i\ (0 \leq v_i, w_i \leq 10^9)\),表示每个物品的体积和价值。
题解
位运算中非常经典的算法:试填法
对于F, 数据范围1e9, 可以从高位开始, 用试填法, 逐位确定,1e9也就是最多到二进制的第30位(\(10^9 < 2^{30}\)),我们从第30位开始试填
从高位开始判断是否能填1,高位能填一个1形成的物品价值比其后所有低位全为1的价值更大,所以我们优先判断高位能否填1
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
int n, k, ans;
int v[N], w[N];
void solve()
{
cin >> n >> k;
for (int i = 0; i < n; i ++ ) cin >> v[i] >> w[i];
for (int i = 30; i >= 0; i -- ) //从二进制第30位开始枚举答案,从高位向下枚举,确保高位先填到1使得价值更大
{
int guess = ans | (1 << i); //尝试在第i位填1,临时存储当前答案,用于后面判断是否合法 使用|运算,继承ans的其他位 仅将第i位改为1
int v_sum = (1 << 30) - 1; //初始化装满最大价值i所需容积 保证有29位1,方便找到第一个合法物品时进行&运算后直接变成该物品的体积
for (int j = 0; j < n; j ++ ) //枚举所有物品 找满足条件的
{
if ((w[j] & guess) == guess) //核心代码 如果当前枚举到的物品满足guess上1的要求
v_sum &= v[j]; //加上该物品
}
if (v_sum <= k) //如果满足题目要求的背包容积 此时的guess就是符合要求的,也就是第i位能填1,将ans赋值为guess
{
ans = guess;
}
}
cout << ans << endl; //填完所有位后输出答案
return;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
return 0;
}