寒假训练2024/1/28
2024/1/28
ABC337(A-E)
A - Scoreboard
思路:
水题,统计加和,最后比较。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int n;
cin >> n;
int A = 0, B = 0, a, b;
for (int i = 0; i < n; i++) {
cin >> a >> b;
A += a;
B += b;
}
if(A > B) {
cout << "Takahashi\n";
}
else if(A < B) {
cout << "Aoki\n";
}
else {
cout << "Draw\n";
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}
B - Extended ABC
题意:
验证给定的字符串是不是ABC扩展串,这个水题思路很多,但是也有人错,需要注意下。
思路:
我们不用搞花里胡哨的思路,正难则反,想什么时候是不符合条件的,显然,如果A前面有B或者C,B前面有C都是不合法的。特判就行。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
string s;
cin >> s;
bool flag_a = 0, flag_b = 0, flag_c = 0;
bool ok = 1;
for (auto c : s) {
if(c == 'A') {
flag_a = 1;
if(flag_c || flag_b) {
ok = 0;
break;
}
}
else if(c == 'B') {
flag_b = 1;
if(flag_c) {
ok = 0;
break;
}
}
else if(c == 'C') {
flag_c = 1;
}
}
if(ok) {
cout << "Yes\n";
}
else {
cout << "No\n";
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}
C - Lining Up 2
题意:
题目给了n个人跟在谁后面,如果是-1就是第一个。
让我们求排队的顺序。
思路:
用数组模拟链表,统计每个人的后继,最后遍历就行。
//数组模拟链表
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int n;
cin >> n;
vector<int>ne(n + 1);
for (int i = 1, t; i <= n; i++) {
cin >> t;
if(t == -1) {
t = 0;
}
ne[t] = i;
}
for (int i = 0; ne[i]; i = ne[i]) {
cout << ne[i] << " ";
}
cout << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}
D - Cheating Gomoku Narabe
题意:
题录给一个图,o表示有,.表示可以有,代价是1,x是不允许有。
让我们找到连续的横或者竖着的k个字符,求最小代价。
思路:
刚开始我是想着遍历每个点,由上面或者左边延申的长度和代价,在遍历过程中一直最小化这个代价。
struct inf {
//竖
int num1;
int cos1;
//横
int num2;
int cos2;
};
vector<string> arr(r);
for (int i = 0; i < r; i++) {
cin >> arr[i];
}
vector<vector<inf>> v(r, vector<inf>(c));
int m = max(r, c);
vector<int> cnt(m + 1, 1e18);
for (int i =0; i < r; i++) {
for (int j = 0; j < c; j++) {
if(arr[i][j] == 'o') {
if(i - 1 >= 0) {
v[i][j].num1 = v[i - 1][j].num1 + 1;
v[i][j].cos1 = v[i - 1][j].cos1;
}
else {
v[i][j].num1 = 1;
v[i][j].cos1 = 0;
}
if(j - 1 >= 0) {
v[i][j].num2 = v[i][j - 1].num2 + 1;
v[i][j].cos2 = v[i][j - 1].cos2;
}
else {
v[i][j].num2 = 1;
v[i][j].cos2 = 0;
}
}
else if(arr[i][j] == '.') {
if(i - 1 >= 0) {
v[i][j].num1 = v[i - 1][j].num1 + 1;
v[i][j].cos1 = v[i - 1][j].cos1 + 1;
}
else {
v[i][j].num1 = 1;
v[i][j].cos1 = 1;
}
if(j - 1 >= 0) {
v[i][j].num2 = v[i][j - 1].num2 + 1;
v[i][j].cos2 = v[i][j - 1].cos2 + 1;
}
else {
v[i][j].num2 = 1;
v[i][j].cos2 = 1;
}
}
else {
v[i][j].num1 = 0;
v[i][j].cos1 = 0;
v[i][j].num2 = 0;
v[i][j].cos2 = 0;
}
cnt[v[i][j].num1] = min(cnt[v[i][j].num1], v[i][j].cos1);
cnt[v[i][j].num2] = min(cnt[v[i][j].num2], v[i][j].cos2);
}
}
但是最后一个样例没过,我才发现,这个思路的错误在于,只能从最左边和最上边第一个合法的字符(o或者。)开始,但是最优解可能是中间开始,所以这个方法只能用来求最多能形成的长条和竖条有多长,却不一定是最优解。
昨晚没想出来就睡觉了。
今天早晨醒来又想了想,对于每个点我们都要遍历,可以分横竖两次遍历,我们按照横遍历讨论:
我第一想的是尺取法,对于每一行,我们枚举左端点和右端点(长度是k),只要这一段里没有x就是合法的,代价就是这一段里。的数量。取每一段的复杂度是O(r * c).但是比较的复杂度是O(r * c).这样复杂度就是面积的平方。会超时,所以要优化一下。
我又想到,比较的算法可以向KMP里的比较优化,因为中间可以不回溯:如果上次的横条是合法的,那么我们只需要特判下一个新的元素就行,如果中间遇到x,这次比较就终止,从x的下一个开始新一轮的比较。
其实就是滑动窗口,用双指针实现。
复杂度是O(S).
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int r, c, k;
cin >> r >> c >> k;
vector<string> arr(r);
for (int i = 0; i < r; i++) {
cin >> arr[i];
}
int minm = 1e18;
for (int i = 0; i < r; i++) {
bool ok = 0;
int res = 0;
for (int pb1 = 0; pb1 <= c - k; pb1++) {
if(ok) {
res -= (arr[i][pb1 - 1] == '.');
if(arr[i][pb1 + k - 1] == 'o') {
minm = min(minm, res);
}
else if(arr[i][pb1 + k - 1] == '.') {
minm = min(minm, ++res);
}
else {
ok = 0;
}
}
else {
res = 0, ok = 1;
for (int pb2 = pb1; pb2 < pb1 + k; pb2++) {
if(arr[i][pb2] == 'o') {
continue;
}
else if(arr[i][pb2] == '.') {
res++;
}
else {
ok = 0;
pb1 = pb2;
break;
}
}
if(ok) {
minm = min(minm, res);
}
}
}
}
for (int j = 0; j < c; j++) {
bool ok = 0;
int res = 0;
for (int pb1 = 0; pb1 <= r - k; pb1++) {
if(ok) {
res -= (arr[pb1 - 1][j] == '.');
if(arr[pb1 + k - 1][j] == 'o') {
minm = min(minm, res);
}
else if(arr[pb1 + k - 1][j] == '.'){
minm = min(minm, ++res);
}
else {
ok = 0;
}
}
else {
res = 0, ok = 1;
for (int pb2 = pb1; pb2 < pb1 + k; pb2++) {
if(arr[pb2][j] == 'o') {
continue;
}
else if(arr[pb2][j] == '.') {
res++;
}
else {
ok = 0;
pb1 = pb2;
break;
}
}
if(ok) {
minm = min(minm, res);
}
}
}
}
if(minm == 1e18) {
cout << -1 << endl;
}
else {
cout << minm << endl;
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}
E - Bad Juice
题意:
又n瓶饮料,只有一瓶有问题,让找到最少的人,对于每个人可以给他喝任意瓶饮料,交互机会给出这几个人的状态,最后要判断是哪一瓶饮料的问题。
思路:二进制状态压缩
之前做过类似的题:
是n瓶饮料,任意瓶饮料有问题,这个是只有一瓶有问题。
异曲同工,上一个问题是需要$2^n$的人能试出来,对于这一瓶我们只需要$\log_{2}{n}$ 就可以。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int n;
cin >> n;
int m = log2(n);
if(pow(2, m) < n) {
m++;
}
cout << m << endl;
vector<vector<int>> v(m, vector<int>() );
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if((i >> j) & 1) {
v[j].push_back(i + 1);
}
}
}
for (int i = 0; i < m; i++) {
cout << v[i].size() << " ";
for (auto it : v[i]) {
cout << it << " ";
}
cout << endl;
}
string res;
cin >> res;
int ans = 1;
int pw = 1;
for (int i = 0; i < res.size(); i++) {
ans += (res[i] - '0') * pw;
pw *= 2;
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}
uva294
思路:
水题,虽然U和V的范围比较大,但是U - V的差值比较小,直接遍历找就行。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int f(int x) {
set<int> s;
for (int i = 1; i * i <= x; i++) {
if(x % i == 0) {
s.insert(i);
s.insert(x / i);
}
}
return s.size();
}
void solve() {
int a, b;
cin >> a >> b;
int maxm = 0, num;
for (int i = a; i <= b; i++) {
int t = f(i);
if(maxm < t) {
maxm = t;
num = i;
}
}
printf("Between %lld and %lld, %lld has a maximum of %lld divisors.\n", a, b, num, maxm);
}
signed main() {
// ios::sync_with_stdio(0);
// cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) {
solve();
}
return 0;
}
codeforce 920(div3 A - E)
A - Square
题意:
给矩形的四个顶点坐标,求矩形的面积。
思路:
就是根据坐标求矩形的长宽,我写的比较麻烦。我想的是矩形的四个点坐标就是横着两个数字的差值和竖着两个数字差值的乘积。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
set<int>a, b;
for (int i = 0; i < 4; i++) {
for (int j = 0, t; j < 2; j++) {
cin >> t;
if(j) {
a.insert(t);
}
else {
b.insert(t);
}
}
}
vector<int>v1;
for (auto it : a) {
v1.push_back(it);
}
vector<int>v2;
for (auto it : b) {
v2.push_back(it);
}
cout << (v1[1] - v1[0]) * (v2[1] - v2[0]) << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) {
solve();
}
return 0;
}
B - Arranging Cats
题意:
给两个字符串s和t,求s得到t的最少步骤
操作1,s中的1变成0,0变成1。
操作2, 选中s中的两个字符,0 和 1交换。
思路:
我直接瞎造,因为对于操作2不会改变字符串中1的数量,所以我先统计两个字符串中1的数量,把差值加到res中。
然后就是把两个字符串中不同的个数加到res中,
最后得出的结果什么神奇的就是答案的两倍,我除二提交了一下就AC了(玄学)。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int n;
cin >> n;
string s, t;
cin >> s >> t;
int sum1 = 0, sum2 = 0;
int res = 0;
for (int i = 0; i < n; i++) {
sum1 += (s[i] == '1');
sum2 += (t[i] == '1');
if(s[i] != t[i]) {
res++;
}
}
cout << (res + abs(sum1 - sum2)) / 2 << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) {
solve();
}
return 0;
}
赛后我想了一下正解,应该是01和 10 数量的最大值。
底层逻辑就是较大的那个可以先转移给较小的那个,剩下的部分新加入或者新拿走。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve1() {
int n;
cin >> n;
string s, t;
cin >> s >> t;
int num01 = 0, num10 = 0;
for (int i = 0; i < n; i++) {
if(s[i] == '0' && t[i] == '1') {
num01++;
}
if(s[i] == '1' && t[i] == '0') {
num10++;
}
}
cout << max(num01, num10) << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) {
solve1();
}
return 0;
}
C - Sending Messages
题意:
给了几个发消息的时间点,然后给关机的电量消耗和开机的电量消耗,看能否最小化的电量消耗把这些消息都发送完。
思路:
吐槽一下,这个题就是脱裤子放屁,谁家手机关机电量会比开机用的还多,而且这个题你说半天也说不明白,还扯上开机关机时刻和发消息时刻,脑袋瓜子嗡嗡的。
其实就是前缀和,发消息的间隔取开机和关机的最小值,看最后的电量够不够。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int n, f, a, b;
cin >> n >> f >> a >> b;
int la = 0;
int res = 0;
for (int i = 0, t; i < n; i++) {
cin >> t;
res += min((t - la) * a, b);
la = t;
}
if(res < f) {
cout << "YES\n";
}
else {
cout << "NO\n";
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) {
solve();
}
return 0;
}
D - Very Different Array
题意:
给两个序列,一个长n,一个长m,用m长的序列选出n长的序列,使得两个n长的序列对应元素差绝对值的和最大。
思路:
起初我以为是n正序排, m倒叙排, 取n个,求差绝对值的和就行。但是样例过不了。
因为如果是m很分散,两边的值差很大,会导致前n个最大的不是最优。
所以我们考虑贪心,每次取差值最大的一对数,直到取n对为止。
算法:
双指针,two_pointer
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int n, m;
cin >> n >> m;
vector<int>v1, v2;
for (int i = 0, t; i < n; i++) {
cin >> t;
v1.push_back(t);
}
for (int i = 0, t; i < m; i++) {
cin >> t;
v2.push_back(t);
}
sort(v1.begin(), v1.end());
sort(v2.begin(), v2.end(), greater<int>());
int res = 0;
int pb1 = 0, pb2 = n - 1;
int pb3 = 0, pb4 = m - 1;
for (int i = 0; i < n; i++) {
int a = abs(v1[pb1] - v2[pb3]);
int b = abs(v1[pb1] - v2[pb4]);
int c = abs(v1[pb2] - v2[pb3]);
int d = abs(v1[pb2] - v2[pb4]);
int maxm = max(a, max(b, max(c, d)));
// cout << maxm << endl;
if(maxm == a) {
res += abs(v1[pb1++] - v2[pb3++]);
}
else if(maxm == b) {
res += abs(v1[pb1++] - v2[pb4--]);
}
else if(maxm == c) {
res += abs(v1[pb2--] - v2[pb3++]);
}
else if(maxm == d) {
res += abs(v1[pb2--] - v2[pb4--]);
}
}
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) {
solve();
}
return 0;
}
E - Eat the Chip
题意:
Alice和Bob下棋,Alice走的方向是左下,正下,右下,Bob是左上,正上,右上。
如果其中一个人能在自己的回合内碰到另一个人,就获胜。
思路:
因为两个人走的方向都要向中间靠拢,所以处于同一行之前能够相遇就能分出胜负,否则就是平局。
这样时间就有了,对时间进行奇偶分类,因为题目说了Alice先走,所以如果时间是奇数Alice肯定会多走一步,那么这种情况就是Alice获胜或者平局,反之就是Bob获胜或者平局。
剩下的我们讨论什么时候获胜什么时候是平局,我就开始模拟两个人,一个追,一个逃,列了很多情况,但是还是不全。
这个时候我看了大佬的博客,看到了一种奇妙的做法:直接求每个人能够到达的边界,通过min(c) 和 max(0), 进行限制,就能很好的得出一个人能够到达的左右边界,然后我们看范围,如果一个人的范围能够覆盖另一个人的范围就获胜。
#include <bits/stdc++.h>
using namespace std;
#define int long long
void solve() {
int r, c, xa, ya, xb, yb;
cin >> r >> c >> xa >> ya >> xb >> yb;
int drt = xb - xa;
int t = drt/ 2;
if(drt < 0) {
cout << "Draw\n";
}
else if(drt % 2 == 1) {
//Alice先走,所以肯定是这种情况是Alice赢或者平局
int la = max(1ll, ya - t - 1);
int ra = min(c, ya + t + 1);
int lb = max(1ll, yb - t);
int rb = min(c, yb + t);
if(la <= lb && ra >= rb) {
cout << "Alice\n";
}
else {
cout << "Draw\n";
}
}
else {
//这种情况是Bob赢或者平局
int la = max(1ll, ya - t);
int ra = min(c, ya + t);
int lb = max(1ll, yb - t);
int rb = min(c, yb + t);
if(lb <= la && rb >= ra) {
cout << "Bob\n";
}
else {
cout << "Draw\n";
}
}
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) {
solve();
}
return 0;
}
F. Sum of Progression
根号分治 的变式,有空再回来补。