牛客周赛 Round 71 题解
牛客周赛 Round 71 题解
A 构造A+B
容易想出最多有 \(n - 1\) 种构造方法,所以只要判断 \(n\) 和 \(k\) 的关系即可。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, k; cin >> n >> k;
if (k <= n - 1) {
cout << "YES\n";
} else {
cout << "NO\n";
}
return 0;
}
B 宝石手串
遇到这种问题,我们先思考什么情况下输出 \(-1\) :当手串内没有重复出现的字符时。所以这个需要加特判。
然后思考,为了去掉最少的宝石,我们需要找到(在环上)离得最近的两个宝石,然后去掉它们中间的宝石即可。如果需要考虑环的话,那么我们在串的遍历上需要从尾回到头,这样是很麻烦的。事实上对于这种环上的问题,我们可以使用「破环成链」的方法。
具体来看,就是将原字符串 \(S\) 变成 \(S + S\) ,这样就可以让头尾相连,然后就将原来的环上问题转化为了串上问题。
对于拼接后的字符串,我们遍历一遍找到最近的两个相同字符的位置即可,记得判断 \(-1\) 的情况。
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
void solve() {
int n; cin >> n;
string s; cin >> s;
if (n == 2) {
if (s[0] != s[1]) cout << -1 << endl;
else cout << 0 << endl;
return ;
}
for (int i = 0; i < n - 1; i ++ ) {
if (s[i] == s[i + 1]) {
cout << 0 << endl;
return ;
}
}
if (s[0] == s[n - 1]) cout << 0 << endl;
else {
s = s + s;
vector<int> pos(30, -1);
int ans = INF;
for (int i = 0; i < 2 * n; i ++ ) {
int c = s[i] - 'a';
if (pos[c] != -1 && pos[c] != i - n) {
ans = min(ans, i - pos[c] - 1);
}
pos[c] = i;
}
if (ans == INF) ans = -1;
cout << ans << endl;
}
}
int main() {
int T; cin >> T;
while (T -- )
solve();
return 0;
}
C 最小循环节
脑筋急转弯题。首先我们思考一个问题:假如说原字符串中只有 abcd
四种字符,那么在添加的时候有没有必要加入没出现过的字符?
其实是没必要的,例如对于 \(abcd\) 来说,我们可以只用 \(abcd\) 四种字符构造,可以这样 \(\underline{a}bcda\underline{b}cdab\underline{c}dabc\underline{d}\) 其中下划线是原来的字符串中的内容。
所以最后的答案就是原字符串中字符的种类。
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int p[2222];
void solve() {
string s; cin >> s;
int n = s.size();
int cnt = 0;
for (int i = 0; i < n; i ++ ) {
if (p[s[i]]) continue;
p[s[i]] ++;
cnt ++;
}
cout << cnt << endl;
}
int main() {
int T = 1;
while (T -- )
solve();
return 0;
}
D 气球谜题
官方题解是DP做法,我们这里使用前缀和来做。
由于最后的颜色排列是 \(3\) 的全排列之一,所以我们也是枚举全排列,然后对于每一种排列进行枚举,具体方式如下:
设当前的排列的颜色为 \(a_0, a_1, a_2\) 。
定义前缀和数组 \(pre_{i,j}\) 表示将前 \(i\) 个气球全部变成 \(j\) 颜色所需的时间,这个递推就可以解决,具体看代码。
可以将最后的气球分成三块区域,其中 \([1, i - 1]\) 是颜色 \(a_0\) ,\([i,j-1]\) 是颜色 \(a_1\) ,\([j,n]\) 是颜色 \(a_2\) 。那么当前的消耗就是
看出我们需要枚举两个端点 \(i,j\) 来实现这个,但是这样的话就是 \(O(n^2)\) 的算法。
我们考虑优化,可以只枚举其中一个端点,例如我们从右往左枚举 \(j\),那么我们只需要在 \(j\) 之前找到一个位置 \(i\) ,使得 \(pre_{i,a_0} - pre_{i,a_1}\) 最小即可。这个可以另外开一个数组 \(mn\),\(mn_j\) 表示前 \(j\) 个位置中最小的 \(pre_{i, a_0} - pre_{i,a_1}\),预处理好这个前缀最小值即可。
需要注意的是,可能最后气球只会被染成一种颜色或者两种颜色,所以我们在枚举 \(j\) 的时候需要考虑好范围,不要漏掉特况。
时间复杂度 \(O(n)\) 带点常数。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 10;
int t[N];
LL pre[N][3];
LL mn[N];
int a[3] = {0, 1, 2};
void solve() {
int n;
cin >> n;
string s; cin >> s;
for (int i = 1; i <= n; i ++ ) cin >> t[i];
for (int i = 1; i <= n; i ++ ) {
pre[i][0] = pre[i - 1][0];
pre[i][1] = pre[i - 1][1];
pre[i][2] = pre[i - 1][2];
if (s[i - 1] == '0') {
pre[i][1] += t[i];
pre[i][2] += t[i];
} else if (s[i - 1] == '1') {
pre[i][0] += t[i];
pre[i][2] += t[i];
} else {
pre[i][0] += t[i];
pre[i][1] += t[i];
}
}
LL ans = INF;
do {
mn[1] = pre[1][a[0]] - pre[1][a[1]];
for (int i = 2; i <= n; i ++ ) {
mn[i] = min(mn[i - 1], pre[i][a[0]] - pre[i][a[1]]);
}
for (int i = n + 1; i >= 1; i -- ) {
ans = min(ans, pre[n][a[2]] - pre[i - 1][a[2]] + pre[i - 1][a[1]] + mn[i - 1]);
}
} while (next_permutation(a, a + 3));
cout << ans << endl;
}
int main() {
int T = 1;
while (T -- )
solve();
return 0;
}
E 三角谜题
容易想到,我们可以枚举底边,然后直接找最长的数量大于等于2的边作为腰即可。
由于边长很长,所以可以使用 \(map\) 来当桶。
时间复杂度 \(O(n \log n)\)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
map<int, int> f;
void solve() {
cout << fixed << setprecision(8);
f.clear();
int n; cin >> n;
for (int i = 1; i <= n; i ++ ) {
int x, y;
cin >> x >> y;
f[x] += y;
}
vector<int> v1;
double ans = -1;
bool flag = false;
for (auto kv : f) {
if (kv.second >= 2) {
v1.push_back(kv.first);
}
}
if (v1.empty()) {
cout << -1 << endl;
return ;
}
for (auto kv : f) {
int k = kv.first;
int v = kv.second;
int j = v1.size() - 1;
while (j >= 0 && v1[j] == k && f[v1[j]] == 2) j --;
if (j < 0) continue;
int len = v1[j];
if (len + len <= k) continue;
double s = (double)k * 0.5 * sqrt(1ll * len * len - 1ll * k * k * 0.25);
flag = true;
ans = max(ans, s);
}
if (flag) cout << ans << endl;
else cout << -1 << endl;
}
int main() {
int T = 1;
cin >> T;
while (T -- )
solve();
return 0;
}
F 变化的数组
感觉是一道很诈骗的题目。事实上我们打表可以发现,在经过一定操作次数后 \(a_i \& m\) 的值就变成了 \(0\) 。之后的按位与操作都是无效的。具体的证明我认为 这篇博客 写的就不错,大家可以去看看。
对于某个数 \(a_i\) ,一共经过了 \(k\) 次操作,其中 \(t\) 次操作过后 \(a_i \& m\) 就已经是 \(0\) 了,记经过了 \(j\) 次操作之后的值为 \(a_i^{(j)}\) ,所以有
那么期望值为
直接写就行了,时间复杂度 \(O(n \cdot \log_2 \min(k,30) \cdot \log_2 (1e9+7))\) 当然可以通过预处理把最后一个 \(\log\) 优化掉。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 1e5 + 10;
const int INF = 0x3f3f3f3f;
const int MOD = 1e9 + 7;
const double eps = 1e-11;
LL q_pow(LL a, int b) {
LL ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = ans * a % MOD;
a = a * a % MOD;
}
return ans;
}
void solve() {
int n, m, k;
cin >> n >> m >> k;
LL sum = 0;
int inv2 = q_pow(2, MOD - 2);
LL inv2k = q_pow(inv2, k);
for (int i = 1; i <= n; i ++ ) {
LL x; cin >> x;
LL tmp = x;
LL now1 = 1;
LL now2 = (q_pow(2, k) - 1 + MOD) % MOD;
for (int i = 1; i <= k; i ++ ) {
LL d = x & m;
x += d;
if (d == 0) break;
now1 = now1 * (k - i + 1) % MOD * q_pow(i, MOD - 2) % MOD;
now2 = (now2 - now1 + MOD) % MOD;
tmp = (tmp + now1 * (x % MOD) % MOD) % MOD;
}
tmp = (tmp + now2 * (x % MOD) % MOD) % MOD;
tmp = tmp * inv2k % MOD;
sum = (sum + tmp) % MOD;
}
cout << sum << endl;
}
int main() {
#ifndef ONLINE_JUDGE
freopen("1.in", "r", stdin);
freopen("1.out", "w", stdout);
#endif
int T = 1;
while (T -- )
solve();
return 0;
}