【题解】AtCoder Beginner Contest 448 A~F
【题解】AtCoder Beginner Contest 448 A~F
A - chmin
思路
简单模拟
Code
#include <iostream>
using namespace std;
const int N = 128;
int a[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n, x;
cin>>n>>x;
for(int i=1;i<=n;i++) {
cin>>a[i];
if(x > a[i]) {
x = a[i];
cout<<"1"<<endl;
} else {
cout<<"0"<<endl;
}
}
}
B - Pepper Addiction
思路
贪心,胡椒能加就加,统计累积即可。
Code
#include <iostream>
using namespace std;
const int N = 1024;
int a[N], b[N], c[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n, m;
cin>>n>>m;
long long tot = 0;
for(int i=1;i<=m;i++) {
cin>>c[i];
}
for(int i=1;i<=n;i++) {
cin>>a[i]>>b[i];
int g = min(c[a[i]], b[i]);
c[a[i]] -= g;
tot += g;
}
cout<<tot<<endl;
}
C
思路
由于 \(K\) 很小,可以直接模拟。
开始时存开一个数组 \(a\) 存球序号-球面数字的映射, \(a[i]\) 表示第 \(i\) 个球表面的数字,然后直接开一个字典 \(s\) 对球面数字计数, 即当前数字为 \(i\) 的球有 \(s[i]\) 个,然后把所有球的数字存进去,对于每组询问,暴力修改 \(s\) ,顺序枚举直到第一个 \(i\) 使得 \(s[i] \neq 0\) 便是本组询问的答案。
Code
#include <iostream>
#include <map>
using namespace std;
const int N = 300005;
const int K = 10;
map<int, int> s;
int a[N];
int r[K];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n, q;
cin>>n>>q;
for(int i=1;i<=n;i++) {
cin>>a[i];
s[a[i]]++;
}
while(q--) {
int k;
cin>>k;
for(int i=1;i<=k;i++) {
cin>>r[i];
s[a[r[i]]]--;
}
for(auto &p: s) {
if(p.second) {
cout<<p.first<<endl;
break;
}
}
for(int i=1;i<=k;i++) {
s[a[r[i]]]++;
}
}
}
D - Integer-duplicated Path
思路
按照题意,是一棵树,所有待求的答案均以 \(1\) 为起点,故不难想到以 \(1\) 作为根。
开一个哈希表 \(s\) 记录当前颜色计数, 然后深搜即可,令当前节点为 \(u\) , \(s[i]\) 表示 \(1\) 到 \(u\) 的简单路径上颜色 \(i\) 的节点数量。
每到一个新的节点,如果 \(s[node[i].c]\) 不为零,表示 \(1\) 到 \(u\) 的父节点的简单路径中,已经存在颜色为 \(node[i].c\) 的节点, \(u\) 及其子树的答案均为 Yes ;否则修改 \(s[node[i].c]++\) 对当前节点进行计数,回溯时记得恢复。
Code
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;
const int N = 200005;
class Node {
public:
vector<int > oe;
int c;
};
Node node[N];
unordered_map<int, int> s;
bool ans[N];
void dfs(int u, int fa, bool flag) {
if(s[node[u].c] || flag) {
ans[u] = true;
flag = true;
}
s[node[u].c]++;
for(int v:node[u].oe) {
if(v != fa) {
dfs(v,u,flag);
}
}
s[node[u].c]--;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin>>n;
for(int i=1;i<=n;i++) {
cin>>node[i].c;
}
for(int i=1;i<n;i++) {
int u, v;
cin>>u>>v;
node[u].oe.push_back(v);
node[v].oe.push_back(u);
}
dfs(1, 0, false);
for(int i=1;i<=n;i++) {
if(ans[i]) {
cout<<"Yes"<<endl;
} else {
cout<<"No"<<endl;
}
}
}
E - Simple Division
思路
矩阵乘法+快速幂+一点点数论
假设不需要对10007取模
很显然,只是要求你构造一个大数,整除 \(M\) 即是答案。
然而如何构造这个大数?
我们可以线性模拟,假设当前的大数为 \(x\) ,重复执行 \(l_i\) 次 \(x = x \times 10 + c_i\) ,如此递推就行,然而这个复杂度 \(O(\Sigma l_i)\) 显然不合理,考虑优化。
注意到重复执行 \(x = x \times 10 + c_i\) 是纯线性操作,可以用矩阵乘法+快速幂来解决,即对于第 \(i\) 行,需要添加 \(l_i\) 个 \(c_i\),我们可以如此构造:
其中 \(j\) 表示从前往后构造的第 \(j\) 位,\(r_j\) 表示现在已经构造了 \(j\) 步的大数,我们就通过一步矩阵乘法往前构造了一位,于是最终构造的大数可以用如下方式计算:
这时优化就显然了, \(B_i^{l_i}\) 可以用矩阵快速幂计算。
可是我们需要对10007取模
当 \(M \neq 10007\) 时如果我们在矩阵乘法中使用 10007 作为模数,则 \(\lfloor N/M \rfloor\) 的结果就会出错, 如果我们用 \(M\) 作为模数,则最后对 10007 取模的结果不正确
注意到
故
综上,我们可以发现解决办法,在矩阵乘法中使用一个大模数 \(10007M\) (实际上可以是 10007 与 \(M\) 的最小公倍数)即可避免上述错误,于是本题就做完了。
Code
#include <iostream>
using namespace std;
typedef long long int64;
const int64 MODER = 10007;
const int64 N = 2;
int64 moder;
class Matrix{
public:
int64 arr[N][N];
Matrix operator*(const Matrix &o) {
Matrix res = {0};
for(int64 i=0;i<N;i++) {
for(int64 j=0;j<N;j++) {
for(int64 k=0;k<N;k++) {
res.arr[i][j] += arr[i][k]*o.arr[k][j]%moder;
}
res.arr[i][j] %= moder;
}
}
return res;
}
};
Matrix A, B;
Matrix E = {
1, 0,
0, 1,
};
Matrix quickPow(Matrix a, int64 x) {
if(x==0) {
return E;
} else if(x == 1) {
return a;
} else {
Matrix t = quickPow(a, x>>1);
Matrix ans = t*t;
if(x&1) {
ans = ans*a;
}
return ans;
}
}
int64 quickPow(int64 a, int64 x) {
if(x==0) {
return 1;
} else if(x == 1) {
return a;
} else {
int64 t = quickPow(a, x>>1);
int64 ans = t*t%MODER;
if(x&1) {
ans = ans*a%MODER;
}
return ans;
}
}
int main() {
int64 k, m;
cin>>k>>m;
moder = (long long)m * MODER;
A.arr[0][0] = 0;
A.arr[0][1] = 1;
A.arr[1][0] = 0;
A.arr[1][1] = 0;
while(k--) {
int64 c, l;
cin>>c>>l;
B.arr[0][0] = 10;
B.arr[0][1] = 0;
B.arr[1][0] = c;
B.arr[1][1] = 1;
A=A*quickPow(B, l);
}
int64 r = A.arr[0][0];
int64 inv = quickPow(m, MODER-2);
int64 ans= ((r-r%m)*inv)%MODER;
cout<<ans<<endl;
}
F - Authentic Traveling Salesman Problem
思路
看上去非常难,实际上非常简单。
一个朴素的想法就是y轴优先,大概是这样:

然而很显然, 这个只需要交替给 \(y=0\) 和 \(y=Y\) 就很容易卡掉,非常朴素的,我们可以考虑分块,假如我们把 \(Y\) 方向分为 \(L\) 块,就会变成这样:

理论上最坏的路径长度为:\((Y/L) \cdot N + 2 \cdot L \cdot X\)
只需要满足以下不等式进行分块即满足要求 (从点1到原点,返回1点等小量偏差忽略不计):
简单移项,就是一个高一学生就该会的解一元二次不等式,可是如果真的这么求会发现,WOC,不等式无解!
难道说要用某些奇妙的构造大法吗?
事实上并不需要,我们注意到,当我们换行时,其实并不一定需要从左侧开始,我们可以奇偶行交替来回,这样就可以省下大约一半的 \(X\) 方向的行程:

此时的最坏路径长度: \((Y/L) \cdot N + L \cdot X\)
求解不等式:
我并没有真的解,反正是有解的,当 \(L=250\) 左右取到最优,足够 cover 某些小量带来的额外行程,故在\(Y\)方向分250块,按照关键字\((X, Y)\)优先,枚举一遍即可。
Code
#include <iostream>
#include <set>
using namespace std;
typedef long long int64;
const int L = 250;
class Node {
public:
int x, y, idx;
bool operator<(const Node &o) const{
if(x != o.x) {
return x<o.x;
} else {
return y<o.y;
}
}
};
set<Node> s[255];
int ans[60005];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin>>n;
int x, y;
int B = 20000000/L;
cin>>x>>y;
for(int i=2;i<=n;i++) {
cin>>x>>y;
s[y/B].insert({
x, y, i
});
}
cout<<"1 ";
for(int i=0;i<=L;i++) {
if(i&1) {
for(auto it = s[i].rbegin(); it!=s[i].rend();it++) {
cout<<it->idx<<" ";
}
} else {
for(auto& node: s[i]) {
cout<<node.idx<<" ";
}
}
}
}
作者在赛时 XY 都分块但是没有处理好,导致卡一个点痛失一题

浙公网安备 33010602011771号