AtCoder Beginner Contest 293
上周因为GDKOI咕咕咕了
A - Swap Odd and Even (abc293 a)
题目大意
给定一个字符串,交换每两个相邻字母,输出结果。
解题思路
模拟即可。
神奇的代码
#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);
string s;
cin >> s;
for(int i = 0; i < s.size(); i += 2)
swap(s[i], s[i + 1]);
cout << s << '\n';
return 0;
}
B - Call the ID Number (abc293 b)
题目大意
每人有一个标号和一个叫号,第\(i\)个人标号为 \(i\),叫号为 \(a_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<int> call(n, 0);
for(int i = 0; i < n; ++ i){
int x;
cin >> x;
-- x;
if (call[i] == 0)
call[x] = 1;
}
int ans = count(call.begin(), call.end(), 0);
cout << ans << '\n';
for(int i = 0; i < n; ++ i){
if (call[i] == 0)
cout << i + 1 << ' ';
}
cout << '\n';
return 0;
}
C - Make Takahashi Happy (abc293 c)
题目大意
二维网格,左上走到右下,一次往右或往下走一格。格子上有数字。
问有多少种路径,其走过的数据互不相同。
解题思路
网格只有\(10 \times 10\),暴力复杂度是 \(O(2^{20})\),直接搜即可。
神奇的代码
#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;
cin >> h >> w;
vector<vector<int>> g(h, vector<int>(w, 0));
for(auto &i : g)
for(auto &j : i)
cin >> j;
int ans = 0;
set<int> qwq;
function<void(int, int)> dfs = [&](int x, int y){
if (x == h - 1 && y == w - 1){
++ ans;
return;
}
if (x != h - 1 && qwq.count(g[x + 1][y]) == 0){
qwq.insert(g[x + 1][y]);
dfs(x + 1, y);
qwq.erase(g[x + 1][y]);
}
if (y != w - 1 && qwq.count(g[x][y + 1]) == 0){
qwq.insert(g[x][y + 1]);
dfs(x, y + 1);
qwq.erase(g[x][y + 1]);
}
};
qwq.insert(g[0][0]);
dfs(0, 0);
cout << ans << '\n';
return 0;
}
D - Tying Rope (abc293 d)
题目大意
\(n\)条绳子, \(m\)个捆绑操作。
每个操作将两个绳子的一端绑起来。一端最多只能被捆绑一次。
问最终得到了多少个环形绳子和非环形绳子。
解题思路
将每个绳子看成一个点,绑起来相当于连一条边。
绳子只有两端意味着每个点至多只有两条边与其相连。
因此每条边,要么是环上的边,要么是链上的边,不会是环里横跨环的边。
即最终的图只有两种:\(x\)个点 \(x\)条边的环,以及 \(x\)个点 \(x-1\)条边的链。
因此我们就统计一下最终图的连通块的数量,做做容斥就出来了。(考虑一条边减少一个连通块)
而统计连通块的数量则用并查集。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
class dsu {
public:
vector<int> p;
vector<int> sz;
int n;
dsu(int _n) : n(_n) {
p.resize(n);
sz.resize(n);
iota(p.begin(), p.end(), 0);
fill(sz.begin(), sz.end(), 1);
}
inline int get(int x) {
return (x == p[x] ? x : (p[x] = get(p[x])));
}
inline bool unite(int x, int y) {
x = get(x);
y = get(y);
if (x != y) {
p[x] = y;
sz[y] += sz[x];
return true;
}
return false;
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int n, m;
cin >> n >> m;
dsu d(n);
for(int i = 0; i < m; ++ i){
int x, y;
string s;
cin >> x >> s >> y >> s;
-- x;
-- y;
d.unite(x, y);
}
int cnt = 0;
for(int i = 0; i < n; ++ i)
cnt += (d.get(i) == i);
int ans1 = m - (n - cnt), ans2 = cnt - ans1;
cout << ans1 << ' ' << ans2 << '\n';
return 0;
}
E - Geometric Progression (abc293 e)
题目大意
给定\(a,x,m\),求 \(\sum_{i=0}^{x-1}a^i \mod m\)
解题思路
因为递推式和求和式都是线性的,因此可以用矩阵来求。
因此就中间那个矩阵的\(x\)次幂 \(\times\)初始矩阵状态就能得到其和 \(s_n\)了。
初始矩阵就是
而\(x\)次幂则用快速幂的形式求即可。
如果用等比数列的话需要求\(a-1\)在 \(m\)下的逆元,而这必须得保证 \(a-1\)与 \(m\)互质,题意没保证,故无法求。
神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
#define FOR(i, x, y) for (decay<decltype(y)>::type i = (x), _##i = (y); i < _##i; ++i)
#define FORD(i, x, y) for (decay<decltype(x)>::type i = (x), _##i = (y); i > _##i; --i)
LL MOD;
struct Mat {
static const LL M = 2;
LL v[M][M];
Mat() { memset(v, 0, sizeof v); }
void eye() { FOR (i, 0, M) v[i][i] = 1; }
LL* operator [] (LL x) { return v[x]; }
const LL* operator [] (LL x) const { return v[x]; }
Mat operator * (const Mat& B) {
const Mat& A = *this;
Mat ret;
FOR (k, 0, M)
FOR (i, 0, M) if (A[i][k])
FOR (j, 0, M)
ret[i][j] = (ret[i][j] + A[i][k] * B[k][j]) % MOD;
return ret;
}
Mat pow(LL n) const {
Mat A = *this, ret; ret.eye();
for (; n; n >>= 1, A = A * A)
if (n & 1) ret = ret * A;
return ret;
}
Mat operator + (const Mat& B) {
const Mat& A = *this;
Mat ret;
FOR (i, 0, M)
FOR (j, 0, M)
ret[i][j] = (A[i][j] + B[i][j]) % MOD;
return ret;
}
void prt() const {
FOR (i, 0, M)
FOR (j, 0, M)
printf("%lld%c", (*this)[i][j], j == M - 1 ? '\n' : ' ');
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
LL a, x, m;
cin >> a >> x >> m;
MOD = m;
Mat qwq;
qwq[0][0] = a;
qwq[1][0] = qwq[1][1] = 1;
Mat ans = qwq.pow(x);
LL res = ans[1][0];
cout << res << '\n';
return 0;
}
F - Zero or One (abc293 f)
题目大意
给定一个数字\(x\),问多少个 \(b\),使得 \(x\)在 \(b\)进制下,每个数位要么是 \(0\)要么是 \(1\)。
多组询问。
解题思路
随着\(b\)增大, \(x\)在 \(b\)进制下的数位的数量是越来越小的。
我们可以暴力判断小的进制是否符合要求,对于大的进制,因为位数很少,可以花 \(2^n\)枚举每个数位的状态(是\(0\)还是\(1\)),再二分一下进制\(b\),看看是否存在一个进制满足上述要求。
神奇的代码
#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 t;
cin >> t;
while(t--){
LL x;
cin >> x;
int up = min(5000ll, x);
int ans = 1;
auto check1 = [&](int base){
LL tmp = x;
while(tmp){
if ((tmp % base) > 1)
return false;
tmp /= base;
}
return true;
};
for(int i = 3; i <= up; ++ i){
ans += check1(i);
}
int up2 = log(x) / log(up) + 1;
auto check2 = [&](int s){
__int128 l = up + 1, r = x + 1;
auto solve = [&](__int128 val){
__int128 tmp = 1, sum = 0;
int tmps = s;
for(int i = 0; i < up2; ++ i){
sum += tmp * (tmps & 1);
tmp *= val;
tmps >>= 1;
}
return sum;
};
while(l + 1 < r){
__int128 mid = (l + r) >> 1;
__int128 sum = solve(mid);
if (sum >= 0 && sum <= x) // sum < 0 => overflow => sum > x
l = mid;
else
r = mid;
}
return solve(l) == x;
};
for(int i = 0; i < (1 << up2); ++ i){
ans += check2(i);
}
cout << ans << '\n';
}
return 0;
}
G - Triple Index (abc293 g)
题目大意
给定一个\(n\)个数字的数组\(a\),和 \(q\)组询问,每组询问给定 \(l,r\),问有多少个 \(l \leq i < j < k \leq r\),满足 \(a_i = a_j = a_k\)
解题思路
考虑一个询问,其答案就是\(\sum \binom{cnt[i]}{3}\),其中 \(cnt[i]\)表示这个该询问区间中数字 \(i\)的数量。
注意到当询问区间发生变化时, \(cnt\)数组非常容易维护,且每次移位只有一个 \(cnt\)值发生变化。因此离线莫队即可。
神奇的代码
#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;
vector<int> a(n);
for(auto &i : a)
cin >> i;
vector<LL> cnt(200001, 0);
vector<array<int, 2>> query(q);
for(auto &i : query){
cin >> i[0] >> i[1];
i[0] --;
}
vector<int> id(q, 0);
int blk_num = sqrt(q), blk_size = q / blk_num;
vector<int> belong(n);
for(int i = 0; i < blk_num; ++ i){
for(int j = i * blk_size; j < (i + 1) * blk_size && j < n; ++ j)
belong[j] = i;
}
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int a, int b){
if (belong[query[a][0]] != belong[query[b][0]]){
return belong[query[a][0]] < belong[query[b][0]];
}else {
return bool((query[a][1] < query[b][1]) ^ (belong[query[a][0]] & 1));
}
});
int l = 0, r = 0;
vector<LL> ans(q);
LL cur = 0;
auto mv = [&](int pos, int val){
int num = a[pos];
if (cnt[num] >= 3)
cur -= cnt[num] * (cnt[num] - 1) * (cnt[num] - 2) / 6;
cnt[num] += val;
if (cnt[num] >= 3)
cur += cnt[num] * (cnt[num] - 1) * (cnt[num] - 2) / 6;
};
for(auto &i : id){
int L = query[i][0], R = query[i][1];
debug(L, R);
while(l > L)
mv(-- l, 1);
while(r < R)
mv(r ++, 1);
while(l < L)
mv(l ++, -1);
while(r > R)
mv(-- r, -1);
ans[i] = cur;
}
for(auto &i : ans)
cout << i << '\n';
return 0;
}
Ex - Optimal Path Decomposition (abc293 h)
题目大意
给定一棵树,要求对每个节点涂色,最小化数字\(k\),使得:
- 每个颜色的节点构成一个连通块
- 任意一条简单路径上,节点的颜色种类不超过\(k\)
求该 \(k\)。
解题思路
<++>
神奇的代码