浙江农林大学蓝桥杯程序设计竞赛校选拔赛(同步赛)【题解】
根据我的主观感受排序
H:蒟蒻wzc的填数游戏
知识点:模拟暴力
模拟就完事了
#include <bits/stdc++.h>
using namespace std;
char s[30000];
int g[10][10];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
inline void solve() {
memset(g, 0, sizeof g);
int n; cin >> n;
unordered_map<char, int> f;
f['w'] = 0, f['d'] = 1, f['s'] = 2, f['a'] = 3;
scanf("%s", s + 1);
int cnt = 1;
g[1][1] = 1;
int x = 1, y = 1;
for (int i = 1; s[i]; i ++ ) {
int tmp = f[s[i]];
x = x + dx[tmp], y = y + dy[tmp];
g[x][y] = ++ cnt;
}
for (int i = 1; i <= n; i ++ ) {
for (int j = 1; j <= n; j ++ ) cout << g[i][j] << ' ';
cout << endl;
}
cout << endl;
}
int main() {
int t; cin >> t;
while (t -- ) {
solve();
}
return 0;
}
A:uu与糖果
知识点:模拟暴力
模拟就完事了,注意一下h为0的特判即可
#include <bits/stdc++.h>
using namespace std;
const int N = 2000020;
typedef long long LL;
LL a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
LL n, k, h; cin >> n >> k >> h;
LL res = 0;
if (h == 0) {
for (int i = 1; i <= n; i ++ ) cin >> a[i], res += a[i];
cout << res << endl;
} else {
for (int i = 1; i <= n; i ++ ) {
cin >> a[i];
if (a[i] > k) res += a[i];
else {
LL re = k - a[i];
a[i] += re / h * h;
res += a[i];
}
}
res += h;
cout << res << endl;
}
return 0;
}
F:签不上到?做这题就对了
知识点:模拟暴力
将数中所有数位拆出来,然后从高位到低位遍历,如果之后有比当前大的,就取最大的最后的那个数字交换即可
其实只要判断大于0就行了,这里是考虑全了的写法
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
inline void solve() {
LL n; cin >> n;
LL tmp = n;
vector<int> v;
if (tmp == 0) {
cout << 0 << endl;
} else if (tmp > 0) {
while (tmp) v.push_back(tmp % 10), tmp /= 10;
reverse(v.begin(), v.end());
for (int i = 0; i < v.size(); i ++ ) {
int ma = v[i], poi = i;
for (int j = i; j < v.size(); j ++ ) {
if (ma <= v[j] && v[j] != v[i]) {
ma = v[j];
poi = j;
}
}
if (poi == i) continue;
else {
swap(v[poi], v[i]);
break;
}
}
for (auto ite : v) cout << ite;
cout << endl;
} else {
tmp = -tmp;
while (tmp) v.push_back(tmp % 10), tmp /= 10;
reverse(v.begin(), v.end());
for (int i = 0; i < v.size(); i ++ ) {
int mi = v[i], poi = i;
for (int j = i; j < v.size(); j ++ ) {
if (mi >= v[j] && v[j] != v[i]) {
mi = v[j];
poi = j;
}
}
if (poi == i) continue;
else {
swap(v[poi], v[i]);
break;
}
}
cout << '-';
bool is = false;
for (auto ite : v) {
if (ite == 0 && is == false) {
is = true;
continue;
} else cout << ite;
}
cout << endl;
}
}
int main() {
int t; cin >> t;
while (t -- ) {
solve();
}
return 0;
}
B:蒟蒻wzc与方块涂色
知识点:二进制搜索
CF有一道差不多的题目:B. Berland Crossword
CF上是平面,这里是三维,其实是一个道理,我们为了讲解方便,拿一个平面来举例子,其他的面同理
设一个面的四条边分别要涂a,b,c,d块黑色,我们很容易发现拐角的那个方块比较难处理,于是我们直接二进制搜索,用一个四位的二进制数的每一位来表示是否要填,填完后我们验证即可
所以当每条边减去角上的点过后应该大于等于0并且小于等于n - 2,否则不合法,检查下一状态是否合法
这是平面状态下的,如果是立体的,就一个一个面判断即可
一共8个顶点,从\((00000000)_2\)枚举到\((11111111)_2\)一共\(2^8\)的复杂度,加上1000组数据,不会超时
#include <bits/stdc++.h>
using namespace std;
int a[20];
int n;
inline bool change(int p1, int p2, int p3, int p4, int l1, int l2, int l3, int l4) {
if (p1 == 1) l1 -- , l4 -- ;
if (p2 == 1) l1 -- , l2 -- ;
if (p3 == 1) l2 -- , l3 -- ;
if (p4 == 1) l3 -- , l4 -- ;
if (l1 < 0 || l2 < 0 || l3 < 0 || l4 < 0) return false;
if (l1 > n - 2 || l2 > n - 2 || l3 > n - 2 || l4 > n - 2) return false;
return true;
}
inline bool check(int x) {
vector<int> v;
v.push_back(123);
for (int i = 0; i < 8; i++) v.push_back(x >> i & 1);
//第一面
if (!change(v[1], v[2], v[3], v[4], a[1], a[2], a[3], a[4])) return false;
//第二面
if (!change(v[1], v[8], v[5], v[4], a[7], a[6], a[5], a[4])) return false;
//第三面
if (!change(v[8], v[7], v[6], v[5], a[8], a[9], a[10], a[6])) return false;
//第四面
if (!change(v[2], v[7], v[6], v[3], a[11], a[9], a[12], a[2])) return false;
//第五面
if (!change(v[8], v[7], v[2], v[1], a[8], a[11], a[1], a[7])) return false;
//第六面
if (!change(v[5], v[6], v[3], v[4], a[10], a[12], a[3], a[5])) return false;
return true;
}
inline void solve() {
cin >> n;
for (int i = 1; i <= 12; i ++ ) cin >> a[i];
for (int i = 0; i < (1 << 8); i ++ ) {
if (check(i)) {
puts("YES");
return ;
}
}
puts("NO");
}
int main() {
int t; cin >> t;
while (t -- ) solve();
return 0;
}
#include <iostream>
using namespace std;
int a[120], b[120];
int main() {
int t;
cin >> t;
while(t--) {
int n, k, x;
int the_max = 0;
cin >> n >> k >> x;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n - 1; i ++) {
if(!(a[i] == a[i + 1] && a[i] == x)) continue;
//cout << i << 'c' << endl;
for(int j = 1; j <= i; j++) {
b[j] = a[j];
}
b[i + 1] = x;
for(int j = i + 2; j <= n + 1; j++) {
b[j] = a[j - 1];
}
b[n + 2] = 0;
//for(int i = 1; i <= n + 1; i++) cout << b[i] << ' ';cout << endl;
int head = i, tail = i + 2, cnt = 0, col = x, col1 = x, col2 = x, flag = 1;
while(head >= 1 && tail <= n + 1) {
//cout << col1 << ' ' << col2 << endl;
if(col1 != col2) break;
//cout << col1 << ' ' << col2 << endl;
col = col1; head--, tail++;
while(b[head] == col && head >= 1) {
cnt++; head--;
flag = 1;
//cout << 'b' << '+' << endl;
}
col1 = b[head];
while(b[tail] == col && tail <= n + 1) {
cnt++; tail++;
flag = 1;
//cout << 'c' << '+' << endl;
}
col2 = b[tail];
if(flag == 1) cnt += 2;
//cout << "+" << endl;
else break;
flag = 0;
}
//cout << cnt << endl;
if(cnt > the_max) the_max = cnt;
}
cout << the_max << endl;
}
}
I:集训队长魂牵梦绕的题目
知识点:模拟
这题在CF上也有一个差不多的:B. Minimal Cost
不过思想很简单:我们把可以阻挡去路的两个点组叫做障碍,我们只需要在每次修改过后查看障碍个数即可
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 20;
int a[N], b[N];
int main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int n; cin >> n;
int cnt = 0;//记录障碍个数
for (int i = 1; i <= n; i ++ ) cin >> a[i];
for (int i = 1; i <= n; i ++ ) cin >> b[i];
for (int i = 2; i <= n; i ++ ) {
if (a[i] == 0) continue;
if (b[i - 1] == 1) cnt ++ ;
if (b[i] == 1) cnt ++ ;
if (b[i + 1] == 1) cnt ++ ;
}
int q; cin >> q;
if (cnt == 0) cout << "I'm the worst friend.Please forgive me." << endl;
else cout << "You are the worst friend." << endl;
while (q -- ) {
int x, y; cin >> x >> y;
if (x == 1) {
if (a[y] == 1) {
if (b[y] == 1) cnt -- ;
if (b[y - 1] == 1) cnt -- ;
if (b[y + 1] == 1) cnt -- ;
a[y] = 0;
} else {
if (b[y] == 1) cnt ++ ;
if (b[y - 1] == 1) cnt ++ ;
if (b[y + 1] == 1) cnt ++ ;
a[y] = 1;
}
} else {
if (b[y] == 1) {
if (a[y] == 1) cnt -- ;
if (a[y - 1] == 1) cnt -- ;
if (a[y + 1] == 1) cnt -- ;
b[y] = 0;
} else {
if (a[y] == 1) cnt ++ ;
if (a[y - 1] == 1) cnt ++ ;
if (a[y + 1] == 1) cnt ++ ;
b[y] = 1;
}
}
if (cnt == 0) cout << "I'm the worst friend.Please forgive me." << endl;
else cout << "You are the worst friend." << endl;
}
return 0;
}
G:来上数学课
知识点:贪心
我们通过自己模拟可以发现只要在它即将加倍的那一题让他答错,并且是在尽量后面的题目中答错即可,知道了这个策略我们写出代码也不难了
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 9;
inline LL q_pow(LL a, LL b) {
LL ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
}
return ans;
}
int main() {
int n, m, k;
while (cin >> n >> m >> k) {
LL cnt = n / k;
if (cnt <= n - m) cout << m << endl;
else {
LL tmp = cnt - n + m;
LL ans = k * (q_pow(2, tmp + 1) - 2 + mod) % mod;
ans = (ans + m - (k * (cnt - n + m))) % mod;
cout << ans << endl;
}
}
return 0;
}
C:排列组合之王
知识点:位运算
位运算中我们只需要计算每一个二进制数位上的贡献即可,因此我们用一个数m来记下每位是否有出现1,因为有1就有贡献,没有1的话永远也不会有1
最后通过打表发现在这样的情况下每位的贡献就是\(2^{n-1}\),暂时不知道如何证明(蹲一个大佬)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mmod = 1e9 + 7;
inline LL q_pow(LL a, LL b, int mod) {
LL ans = 1;
for (; b; b >>= 1) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
}
return ans;
}
int main() {
int n; cin >> n;
LL m = 0;
for (int i = 1; i <= n; i ++ ) {
int x; cin >> x;
m |= x;
}
LL pp = q_pow(2, n - 1, mmod);
cout << m * pp % mmod;
return 0;
}
D:异或游戏
知识点:Trie,前缀和
就是个板子题,然后用前缀和来维护前缀的最小值,后缀和来维护后缀的最大值(这里的前缀后缀和都指异或和)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 6000060;
struct node {
LL cnt = 0, ch[N][2];
void input(LL x) {
int u = 0;
for (int i = 31; ~i; i -- ) {
LL c = (x >> i) & 1;
if (!ch[u][c]) ch[u][c] = ++ cnt;
u = ch[u][c];
}
}
LL find_same(LL x) {
LL ans = 0, u = 0;
for (int i = 31; ~i; i -- ) {
LL c = (x >> i) & 1, cc = c ^ 1;
if (ch[u][c]) u = ch[u][c], ans <<= 1;
else u = ch[u][cc], ans = ans << 1 | 1;
}
return ans;
}
LL find_another(LL x) {
LL ans = 0, u = 0;
for (int i = 31; ~i; i -- ) {
LL c = (x >> i) & 1, cc = c ^ 1;
if (ch[u][cc]) u = ch[u][cc], ans = ans << 1 | 1;
else u = ch[u][c], ans <<= 1;
}
return ans;
}
} trie1, trie2;
LL sum_pre[N], sum_last[N];
LL l[N], r[N];
int main() {
int n; cin >> n;
for (int i = 1; i <= n; i ++ ) {
cin >> sum_pre[i];
sum_last[i] = sum_pre[i];
sum_pre[i] ^= sum_pre[i - 1];
l[i] = 1e18;
}
l[0] = 1e18;
for (int i = n; i; i -- ) sum_last[i] ^= sum_last[i + 1];
for (int i = 1; i <= n; i ++ ) {
trie1.input(sum_pre[i - 1]);
l[i] = trie1.find_same(sum_pre[i]);
l[i] = min(l[i], l[i - 1]);
}
for (int i = n; i >= 1; i -- ) {
trie2.input(sum_last[i + 1]);
r[i] = trie2.find_another(sum_last[i]);
r[i] = max(r[i], r[i + 1]);
}
LL ans = -1e18;
for (int i = 1; i < n; i ++ ) ans = max(ans, r[i + 1] - l[i]);
cout << ans << endl;
return 0;
}
E:谁是天选之人
知识点:线段树
其实就是个线段树的板子题,当然这里还有另外一种解法, 用并查集维护
这里感谢SaladDays提供的思路
基本思路就是在这个序列中,每次划定的范围中,一个数是它右边的数的儿子。而我们填数由于以最后一次为准, 所以直接从后往前填即可,这里保证了每个点只遍历到一遍,所以复杂度是O(n)的,具体看代码。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000100;
int par[N], ans[N];
inline int find(int x) {
if (x == par[x]) return x;
return par[x] = find(par[x]);
}
int main() {
int n, m, a, b; cin >> n >> m >> a >> b;
for (int i = 1; i <= n; i ++ ) par[i] = i;
for (int i = m; i; i -- ) {
int l = (i * a + b) % n + 1, r = (i * b + a) % n + 1;
if (l > r) swap(l, r);
int root = find(l);
while (root <= r) {
par[root] = root + 1;
ans[root] = i;
root = find(root);
}
}
for (int i = 1; i <= n; i ++ ) cout << ans[i] << endl;
return 0;
}