AtCoder Beginner Contest 278
A - Shift (abc278 a)
题目大意
给定一个有\(n\)个整数的数组\(a\),要求进行以下 \(k\)次操作,输出操作后的数组。
操作为:将第一个数去掉,在队尾加上一个\(0\)。
解题思路
模拟即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, k;
cin >> n >> k;
k = min(n, k);
int x;
for(int i = 0; i < k; ++ i){
cin >> x;
}
for(int i = k; i < n; ++ i){
cin >> x;
cout << x << ' ';
}
for(int i = 0; i < k; ++ i)
cout << 0 << ' ';
return 0;
}
B - Misjudge the Time (abc278 b)
题目大意
定义一个迷惑时间为:交换小时的个位和分钟的十位后,所形成的时间也是有效时间。
给定一个时间\(h:m\),问该时间(包括该时间)之后第一个迷惑时间是多少。
解题思路
时间数就只有\(24 \times 60 = 1440\),逐一枚举判断即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int h, m;
cin >> h >> m;
auto check = [](int h, int m){
int h1 = h / 10;
int h2 = h % 10;
int m1 = m / 10;
int m2 = m % 10;
int H = h1 * 10 + m1;
int M = h2 * 10 + m2;
return H >= 0 && H <= 23 && M >= 0 && M <= 59;
};
auto add = [](int &h, int &m){
++ m;
if (m >= 60){
m -= 60;
h ++;
}
h %= 24;
};
while(true){
if (check(h, m)){
cout << h << ' ' << m << '\n';
break;
}
add(h, m);
}
return 0;
}
C - FF (abc278 c)
题目大意
一个聊天软件,\(n\)位用户,有 \(q\)次事件,第一个事件为 \(a\)关注 \(b\),第二个是取关,第三个问 \(a\)和 \(b\)之间是否是互关的关系。
解题思路
因为用户标号达到\(10^9\),不能直接二维数组,用set
或map
记录关注信息,直接判断即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, q;
cin >> n >> q;
set<pair<int, int>> qwq;
while(q--){
int t, a, b;
cin >> t >> a >> b;
if (t == 1){
qwq.insert({a, b});
}else if (t == 2){
qwq.erase({a, b});
}else{
if (qwq.find({a, b}) != qwq.end() && qwq.find({b, a}) != qwq.end())
cout << "Yes" << '\n';
else
cout << "No" << '\n';
}
}
return 0;
}
D - All Assign Point Add (abc278 d)
题目大意
给定一个有\(n\)个整数的数组\(a\),有 \(q\)次操作,分为三类:
- 给定\(x\),表示将所有数赋值为 \(x\)
- 给定\(i\)和 \(x\),将 \(a_i\)增加 \(x\)
- 给定\(i\),询问 \(a_i\)
解题思路
第一个操作相当于规定基准数,第二个操作我们用一个数组\(add\)来维护增加的数,也就是相对于基准数的值。
这样对于第三个操作,\(a_i\)就是两者的加了。
当再一次操作一时,我们把之前进行过操作二的数据\(add\)对应的位置重置为\(0\)即可。总的时间复杂度就是\(O(q)\)(势能分析的角度,每一次操作二会给操作一带来\(1\)的势能,总的势能不超过 \(O(q)\))
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
vector<LL> a(n);
set<int> id;
for(int i = 0; i < n; ++ i)
id.insert(i);
LL base = 0;
for(auto &i : a)
cin >> i;
int q;
cin >> q;
while(q--){
int op;
cin >> op;
if (op == 1){
for(auto &i : id){
a[i] = 0;
}
id.clear();
cin >> base;
}else if (op == 2){
int pos;
LL val;
cin >> pos >> val;
-- pos;
a[pos] += val;
id.insert(pos);
}else {
int pos;
cin >> pos;
-- pos;
cout << base + a[pos] << '\n';
}
}
return 0;
}
E - Grid Filling (abc278 e)
题目大意
给定一个矩形,格子上有数。
给定\(h,w\),意味大小为\(h \times w\)的黑布,该黑布从矩形左上角向右移动,向下移动,直到右下角。问每个位置,将黑布的数遮盖后,剩下未被遮盖的不同的数的个数。
解题思路
暴力即可,黑布往右移动的时候,加上被遮住的一列,减去被遮住的一列。总的时间复杂度为\(O(n^3)\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int H, W, N, h, w;
cin >> H >> W >> N >> h >> w;
vector<vector<int>> a(H, vector<int>(W));
vector<int> cnt(N + 1, 0);
int ans = 0;
for(auto &i : a)
for(auto &j : i){
cin >> j;
cnt[j] ++;
if (cnt[j] == 1)
++ ans;
}
vector<int> tmp(cnt.begin(), cnt.end());
int bac = ans;
for(int i = 0; i <= H - h; ++ i){
for(int j = i; j < i + h; ++ j)
for(int k = 0; k < 0 + w; ++ k){
cnt[a[j][k]] --;
if (cnt[a[j][k]] == 0)
-- ans;
}
cout << ans;
for(int j = 1; j <= W - w; ++ j){
for(int k = i; k < i + h; ++ k){
cnt[a[k][j - 1]] ++;
if (cnt[a[k][j - 1]] == 1)
++ ans;
}
for(int k = i; k < i + h; ++ k){
cnt[a[k][j + w - 1]] --;
if (cnt[a[k][j + w - 1]] == 0)
-- ans;
}
cout << ' ' << ans;
}
cout << '\n';
ans = bac;
cnt = tmp;
}
return 0;
}
也可以记录一个二维前缀和\(cnt[i][j][k]\)表示 \((1,1)-(i,j)\)的矩形中,数字 \(k\)出现的次数。然后对于每次询问通过前缀和得到剩下各个数字出现的次数,再检查非\(0\)的个数。复杂度也是 \(O(n^3)\),且这种更好写一点。
F - Shiritori (abc278 f)
题目大意
给定\(n\)个字符串\(s\), \(a\)和 \(b\)在玩。 \(a\)先手。
每个人说出一个\(1\)~\(n\)数字\(i\),该数字必须是之前未说过的,且\(s_i\)的首字母和\(s_j\)的尾字母相同,其中 \(j\)是上一个人说过的数字。
问两者都是绝顶聪明的情况下,谁赢。
解题思路
设\(dp[s][i]\)表示目前已经选择的数字状态为 \(s\),且最后说过的数字是 \(i\),当前状态是必胜还是必输,枚举后继状态,直接搜索即可。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n;
cin >> n;
vector<vector<int>> edge(n);
vector<pair<int, int>> qwq;
for(int i = 1; i <= n; ++ i){
string s;
cin >> s;
qwq.push_back({s.front(), s.back()});
}
for(int i = 0; i < n; ++ i)
for(int j = 0; j < n; ++ j){
if (i == j)
continue;
if (qwq[i].second == qwq[j].first)
edge[i].push_back(j);
}
vector<vector<int>> dp((1 << n), vector<int>(n, -1));
function<int(int, int)> dfs = [&](int s, int la){
if (dp[s][la] != -1)
return dp[s][la];
int ok = 1;
for(auto nxt : edge[la]){
if ((s >> nxt) & 1)
continue;
ok &= dfs(s | (1 << nxt), nxt);
}
return dp[s][la] = (ok ^ 1);
};
int ok = 1;
for(int i = 0; i < n; ++ i){
ok &= dfs((1 << i), i);
}
if (!ok)
cout << "First" << '\n';
else
cout << "Second" << '\n';
return 0;
}
G - Generalized Subtraction Game (abc278 g)
题目大意
有写有数字\(1\)~\(n\)的牌放在桌子上,给定\(l,r\),你和一个绝顶聪明的人玩一个游戏。你可以选择你先手还是后手,然后轮流进行的操作是:
- 选择一个连续区间,其长度在\([l,r]\)内。然后将桌子上该区间的数字牌全部拿走。注意必须区间上的所有数字牌都在桌子上。
谁无法操作就输。
请写一个程序,打赢这个绝顶聪明的人。
解题思路
蛮有意思的交互题,是一场真·博弈对决。
第一次操作相当于将一个连续的区间一分为二,可以考虑一种经典策略:对称操作。如果将区间分成两个一模一样,那么对方对区间\(a\)做的操作,我都可以对区间 \(b\)做同样的操作。这样一定是对方最先变得无法操作。
将 \([1, n]\)拆成 \([1, x], [x + 1, n - x], [n - x + 1, n]\) ,其中中间那个是丢掉的,很显然中间的区间数量的奇偶性和\(n\)相同(前后两个区间内数量加起来必是偶数)。因此当 \(l == r\)且 \(l\)与 \(n\)奇偶性相等 时,或者\(l < r\)时(此时 \(l\)和 \(l+1\)必定有个与 \(n\)奇偶性相同)都可以采用此策略:选择先手,将区间拆成两个相等的,然后模仿对方行为做即可。
但当 \(l==r\)且 \(l\)与 \(n\)的奇偶性不相等时,此时我们考虑暴力了。
该题本质上还是博弈题。当我们对一个区间一分为二后,新出来的两个区间本质上就是两个独立的游戏,因此此时游戏的\(sg\)值就是这两个区间的 \(sg\)值的异或。
因此我们求得所有长度的\(sg\)值,先根据初态是必胜态还是必败态选择先后手,然后枚举选择一种操作使得下一个局面是必败态(对方必败)。
预处理\(sg\)值的复杂度是 \(O(n^2)\),即枚举当前长度,然后枚举操作起点(操作长度是固定的为 \(l\))。
后来枚举下一步操作的复杂度是 \(O(n)\)。
事实上 \(l \neq r\)的话,两者的复杂度都要乘以 \(n\)。不过有人说可以在\(O(n^2\log n)\)的时间内预处理,每次操作的复杂度是\(O(n\log n)\)
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, l, r;
cin >> n >> l >> r;
if (r - l >= 1 || ((l & 1) == (n & 1))){
int op = l + ((l & 1) != (n & 1));
int left = (n - op) >> 1;
cout << "First" << endl;
cout << left + 1 << ' ' << op << endl;
int x, y;
while(true){
cin >> x >> y;
if (x == 0 || x == -1)
break;
if (x <= left){
cout << x + left + op << ' ' << y << endl;
}else {
cout << x - left - op << ' ' << y << endl;
}
}
}else{
vector<int> sg(n + 1, 0);
vector<int> cnt(n + 5, 0);
for(int i = 1; i <= n; ++ i){
for(int j = 1; j <= i; ++ j){
if (j + l - 1 > i)
break;
cnt[sg[j - 1] ^ sg[i - (j + l - 1)]] = 1;
}
int val = 0;
while(cnt[val])
++ val;
sg[i] = val;
fill(cnt.begin(), cnt.end(), 0);
}
set<pair<int, int>> s;
s.insert({1, n});
int cur = sg[n];
auto work = [&](){
for(auto &c : s){
for(int i = c.first; i <= c.second; ++ i){
if (i + l - 1 > c.second)
break;
int nxt = cur ^ sg[c.second - c.first + 1] ^ sg[i - c.first] ^ sg[c.second - (i + l - 1)];
if (!nxt)
return i;
}
}
return 0;
};
auto remove = [&](int pos){
for(auto &c : s){
if (pos >= c.first && pos <= c.second){
cur ^= sg[c.second - c.first + 1] ^ sg[pos - c.first] ^ sg[c.second - (pos + l - 1)];
s.insert({c.first, pos - 1});
s.insert({pos + l, c.second});
s.erase(c);
return;
}
}
};
if (sg[n]){
cout << "First" << endl;
int pos = work();
cout << pos << ' ' << l << endl;
remove(pos);
}else{
cout << "Second" << endl;
}
while(true){
int x, y;
cin >> x >> y;
if (x == 0 || x == -1)
break;
remove(x);
int pos = work();
cout << pos << ' ' << l << endl;
remove(pos);
}
}
return 0;
}
Ex - make 1 (abc278 h)
题目大意
<++>
解题思路
<++>
神奇的代码