2023-07-02 01:28阅读: 613评论: 2推荐: 2

AtCoder Beginner Contest 308

这几天在收拾东西搬家,先附上代码,晚点补上题解补完了
感觉这次FG都写不太明白

A - New Scheme (abc308 A)

题目大意

给定八个数,问是否满足以下要求:

  • 不严格升序
  • 每个数在100675之间
  • 每个数都是 25的倍数

解题思路

依次对每个数判断是否符合这三个条件即可。

神奇的代码
#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);
array<int, 8> a;
for(auto &i : a)
cin >> i;
auto ok = [&](){
for(int i = 0; i < 8; ++ i){
if (i && a[i] < a[i - 1])
return false;
if (a[i] < 100 || a[i] > 675)
return false;
if (a[i] % 25)
return false;
}
return true;
};
if (ok())
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}


B - Default Price (abc308 B)

题目大意

高桥吃n份寿司,寿司分为很多种类,其中告诉了 m种寿司的价格,以及没提到的其他种类寿司的统一价格,问需要多少钱。

解题思路

map记录每种寿司的价格,直接累计计算即可。

当然 n只有 100,花 O(m)来计算每份寿司的价格也行。

神奇的代码
#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, m;
cin >> n >> m;
map<string, int> price;
vector<string> a(n);
for(auto &i : a)
cin >> i;
vector<string> col(m);
for(auto &i : col)
cin >> i;
int ano;
cin >> ano;
for(auto &i : col){
int p;
cin >> p;
price[i] = p;
}
int ans = 0;
for(auto &i : a){
if (price.find(i) == price.end())
ans += ano;
else
ans += price[i];
}
cout << ans << '\n';
return 0;
}


C - Standings (abc308 C)

题目大意

抛硬币,给定n个人抛n次硬币后的正面次数 a和反面次数 b

要求给这n个人降序排序,排序的关键字为 aa+b

解题思路

直接排序就好了。

一般最好避免浮点数比较,因为会有误差,在此处我们要比较aiai+biajaj+bj 的正负性,可以改为比较ai(aj+bj)aj(ai+bi)的正负性,这样就避免浮点数了。

神奇的代码
#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<array<int, 2>> p(n);
for(auto &i : p)
cin >> i[0] >> i[1];
vector<int> id(n);
iota(id.begin(), id.end(), 0);
sort(id.begin(), id.end(), [&](int a, int b){
LL l = 1ll * p[a][0] * (p[b][0] + p[b][1]);
LL r = 1ll * p[b][0] * (p[a][0] + p[a][1]);
return l != r ? l > r : a < b;
});
for(auto &i : id)
cout << i + 1 << ' ';
cout << '\n';
return 0;
}


D - Snuke Maze (abc308 D)

题目大意

给定一个二维矩阵,问能否从左上走到右下,要求途径的每个格子上的字母组成 snuke的不断重复拼接的形式。

解题思路

直接搜索即可。

考虑搜索的状态,一个是所处的位置(行和列),另一个是已经走过的距离(为了定位要满足snuke中的哪个字母),其状态数只有O(5nm),转移 O(1),于是直接记忆化即可。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
array<int, 4> dx{1, -1, 0, 0};
array<int, 4> dy{0, 0, 1, -1};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int h, w;
cin >> h >> w;
vector<string> tu(h);
for(auto &i : tu)
cin >> i;
string target = "snuke";
vector<vector<vector<int>>> visit(h, vector<vector<int>>(w, vector<int>(target.size(), 0)));
function<bool(int, int, int)> dfs = [&](int x, int y, int len){
visit[x][y][len] = 1;
if (tu[x][y] != target[len])
return false;
if (x == h - 1 && y == w - 1)
return true;
for(int i = 0; i < 4; ++ i){
int nx = x + dx[i], ny = y + dy[i], nlen = (len + 1) % target.size();
if (nx < 0 || nx >= h || ny < 0 || ny >= w || visit[nx][ny][nlen])
continue;
if (dfs(nx, ny, nlen))
return true;
}
return false;
};
auto ok = [&](){
return dfs(0, 0, 0);
};
if (ok())
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}


E - MEX (abc308 E)

题目大意

给定一个数组a,以及同样长度,仅包括字符 MEX的字符串s

求所有满足 i<j<k,且 sisjsk= MEXmex(ai,aj,ak)的和。

mex(x)函数是求不在 x内的最小非负整数。

解题思路

先考虑找两个字符,比如EX,正常来说我们是从左到右遍历,遇到E时,需要统计所有在该E右边的X的信息,然后得到结果。我们可以从右往左统计每个X对应的数组a的值,这样对于此时的每一个 E,当前统计的信息就是该E右边的X的信息。

考虑要统计什么信息,假设当前的E对应的数组a的值是 e,那么所有 EX的组成的mex只有三种情况:

  • mex(e,0)
  • mex(e,1)
  • mex(e,2)

此时以该E开始,右边的所有X组成的EX,对答案的贡献就分这三类,因此我们就记录一下右边有多少个是0X1X2X。然后乘以对应的mex值就是对答案的贡献。这就是我们要统计的信息。

解决了EX,对于MEX,其实可以看成MEX,只是这时候的EX6种情况:

  • mex(M, 0, 0)
  • mex(M, 0, 1)
  • mex(M, 0, 2)
  • mex(M, 1, 1)
  • mex(M, 1, 2)
  • mex(M, 2, 2)

同样我们就记录EX分别是00,01,02,11,12,22这些情况的数量,再乘以对应的 mex值,就是该M对答案的贡献。而统计EX 的数量就是刚刚的问题。

代码里记录00,01的情况用的二进制状压的方法。

神奇的代码
#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> a(n);
for(auto &i : a)
cin >> i;
string s;
cin >> s;
array<int, 3> x{};
array<LL, 8> ex{};
map<int, int> tr{{0, 1}, {1, 2}, {2, 4}};
map<int, int> mex;
auto get_mex = [&](int x, int y, int z){
set<int> candidate{0, 1, 2, 3};
if (candidate.count(x))
candidate.erase(x);
if (candidate.count(y))
candidate.erase(y);
if (candidate.count(z))
candidate.erase(z);
return *candidate.begin();
};
for(int i = 0; i < 3; ++ i)
for(int j = 0; j < 3; ++ j)
for(int k = 0; k < 3; ++ k){
mex[tr[i] | tr[j] | tr[k]] = get_mex(i, j, k);
}
LL ans = 0;
for(int i = n - 1; i >= 0; -- i){
if (s[i] == 'X'){
x[a[i]] ++;
}else if (s[i] == 'E'){
for(int j = 0; j < 3; ++ j){
int status = (tr[a[i]] | tr[j]);
ex[status] += x[j];
}
}else{
for(int j = 0; j < 8; ++ j){
ans += mex[tr[a[i]] | j] * ex[j];
}
}
}
cout << ans << '\n';
return 0;
}


感觉也可以枚举 E,然后统计左边的 M的信息以及右边X的信息(就是对应的数的个数),然后再枚举它们取值的3\times 3 = 9种情况计算贡献。


F - Vouchers (abc308 F)

题目大意

给定n个商品的价格,以及 m个优惠券,每个优惠券只能用于一个商品,且每个商品购买时只能用一个优惠券。

i个优惠券可以优惠di元,但要求对应商品价格至少 li(lidi)元。

问购买这 n个商品需要的最少价格。

解题思路

有几种思考方向,可以枚举优惠券,也可以枚举商品。枚举优惠券的话,考虑枚举的顺序,比如以di升序, li的降序。枚举商品的话,考虑以商品价格的升序还是降序枚举。就这几种思考方向,分别思考一下或许会发现一些突破口。

可以发现,以商品价格的升序方式考虑的话,会不断有新的优惠券可以使用,且先前旧的未使用的优惠券也可以使用,这是一个很好的性质,因此我们就可以从这些未使用且可使用的优惠券中选优惠力度最大的。

考虑是否存在反例,因为每个商品只能用一个优惠券,因此每次都只会从未使用中选最好的一个。当考虑一个新商品,同时还有更多的优惠券变得可使用时,这些优惠券不能代替先前的优惠券适用(不满足最低价格),因此也替换不了。

感觉这个贪心貌似就对了。

如果考虑商品价格降序枚举的话,会不断有优惠券变得不可使用,考虑使用门槛低优惠力度大的优惠券,会发现这样的做法不太行。

神奇的代码
#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, m;
cin >> n >> m;
vector<int> p(n);
for(auto &i : p)
cin >> i;
vector<array<int, 2>> coupon(m);
for(auto &i : coupon)
cin >> i[1];
for(auto &i : coupon)
cin >> i[0];
priority_queue<array<int, 2>> best{};
sort(p.begin(), p.end());
sort(coupon.begin(), coupon.end(), [](const auto& a, const auto& b){
return a[1] < b[1];
});
int pos = 0;
LL ans = 0;
for(auto &i : p){
while(pos < m && coupon[pos][1] <= i){
best.push(coupon[pos]);
++ pos;
}
int discout = 0;
if (!best.empty()){
discout = best.top()[0];
best.pop();
}
ans += i - discout;
}
cout << ans << '\n';
return 0;
}


G - Minimum Xor Pair Query (abc308 G)

题目大意

初始空数组,维护三种操作:

  • 1 x 往数组里加入一个数x
  • 2 x 往数组里删除一个数x
  • 3 从数组选两个数,使得异或值最小,求这个最小值

解题思路

不考虑操作1,2,仅考虑如何从数组里选两个异或值最小。很显然选择的两个数,在二进制下第一个不同的位置尽可能的在低位。为此我们可以对这n个数建立一棵 trie树,发现该问题可以递归的解决。

考虑一个节点u,左儿子(二进制下为0)lson,右儿子(二进制下为1)rson

  • 如果u下只有两个数,一个数在 lson子树内,一个在 rson内,那以 u节点为根的子树的最小异或值 ansu=lsonrson。一般我们要遍历到叶子才知道这个lson,rson是多少,但为了减少时间复杂度,可以把这个信息往上提到lson,rson处,这样就不用遍历到叶子。
  • 如果lson下有至少两个数,那我们选择的两个数要么都在lson里,要么都不在 lson里。那么以u节点为根的子树的最小异或值 ansu=min(ansu,anslson),而anslson是个子问题。
  • 如果rson下有至少两个数,同样我们选择的两个数要么都在rson里,要么都不在 rson里。那么以u节点的子树的最小异或值 ansu=min(ansu,ansrson)ansrson同样是个子问题。
  • 其余情况ansu都是无穷大。

对于叶子节点,如果该叶子对应两个数,那么 ansleaf=0,否则为无穷大。

考虑修改的话,我们发现上述产生答案的信息(以u节点为根的子树中数的个数 以及 只有一个数时其数是多少,其实就是叶子信息)是可以实时维护的:插入就是个数+1,再判断数的个数是不是 1,是的话同步一下儿子的信息。于是问题就解决了。

两个信息的维护其实都是一个很简单的dp

  • cntu表示以 u节点为根的子树的数的个数,则 cntu=cntlson+cntrson
  • valueu表示以 u节点为根的子树中,如果数的个数为1,其值是多少,则 valueu=valuelson[cntlson==1]+valuerson[cntrson==1]

ansu就根据上述的情况就可以得到了。

考虑到信息的维护,trie树插入得写成递归形式,因为插入后要合并信息,更新答案。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int inf = (1 << 30) + 114514;
const int SZ = 2;
template<typename T, typename K>
struct Trie {
struct node {
K value;
int ans;
int vis_count;
array<int, SZ> children;
node(K val) : value(val) {
vis_count = 0;
fill(children.begin(), children.end(), 0);
ans = inf;
}
};
int cast(K val) {
int ret = val;
assert(ret < SZ and ret >= 0);
return ret;
}
vector<node> tree;
Trie(K val) {
tree.push_back(node(val));
}
void insert(int v, int pos, int cur, const T &sequence, bool remove = false) {
if (remove) {
tree[cur].vis_count -= 1;
} else {
tree[cur].vis_count += 1;
}
if (pos < sequence.size()){
K value = sequence[pos];
if (tree[cur].children[cast(value)] == 0) {
tree[cur].children[cast(value)] = (int) tree.size();
tree.emplace_back(v);
}
insert(v, pos + 1, tree[cur].children[cast(value)], sequence, remove);
if (tree[cur].vis_count == 1){
int lson = tree[cur].children[cast(value)];
int rson = tree[cur].children[cast(value) ^ 1];
if (lson != 0 && tree[lson].vis_count != 0)
tree[cur].value = tree[lson].value;
else
tree[cur].value = tree[rson].value;
}
tree[cur].ans = inf;
int lson = tree[cur].children[0], rson = tree[cur].children[1];
if (lson != 0 && tree[lson].vis_count > 1)
tree[cur].ans = min(tree[cur].ans, tree[lson].ans);
if (rson != 0 && tree[rson].vis_count > 1)
tree[cur].ans = min(tree[cur].ans, tree[rson].ans);
if (lson != 0 && rson != 0 && tree[lson].vis_count == 1 && tree[rson].vis_count == 1)
tree[cur].ans = tree[lson].value ^ tree[rson].value;
}else{
tree[cur].ans = inf;
if (tree[cur].vis_count > 1)
tree[cur].ans = 0;
}
}
void remove(int v, const T& sequence) {
insert(v, 0, 0, sequence, true);
}
int get_min(){
return tree.front().ans;
}
};
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
Trie<array<int, 30>, int> tree(0);
auto tr2bin = [](int x){
array<int, 30> bin{};
for(int i = 0; i < 30; ++ i){
bin[i] = ((x >> (29 - i)) & 1);
}
return bin;
};
int q;
cin >> q;
while(q--){
int op;
cin >> op;
if (op == 1){
int x;
cin >> x;
tree.insert(x, 0, 0, tr2bin(x));
}else if (op == 2){
int x;
cin >> x;
tree.remove(x, tr2bin(x));
}else{
cout << tree.get_min() << '\n';
}
}
return 0;
}


看了看题解发现异或最小有个神奇的性质:其最小值一定相邻两个数之间,这里数是升序排序的。证明的话题解也有,通俗点讲就是考虑证明min(xy,yz)<xz。考虑xz 二进制下从高到低位第一个不同的地方 ,其x0z1(因为 x<z),而 y再此处的值要么是 0要么是 1,肯定与 xz相同,因此其异或值在该位是 0,因此可以更小。

有了这个性质之后,我们可以用一个multiset维护这个序列,用于插入操作、以及删除操作的查找和删除,再用另一个multiset维护相邻两数的异或值。

神奇的代码
#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);
multiset<int> a, ans;
int q;
cin >> q;
while(q--){
int op;
cin >> op;
if (op == 1){
int x;
cin >> x;
if (!a.empty()){
auto nxt = a.upper_bound(x), pos = (nxt == a.begin() ? nxt : prev(nxt));
if (nxt != a.begin()){
ans.extract(*pos ^ *nxt);
ans.insert(*pos ^ x);
}
if (nxt != a.end()){
ans.insert(x ^ *nxt);
}
}
a.insert(x);
}else if (op == 2){
int x;
cin >> x;
auto pos = a.lower_bound(x), prv = (pos == a.begin() ? pos : prev(pos)), nxt = next(pos);
if (pos != a.begin()){
ans.extract(*prv ^ *pos);
}
if (nxt != a.end())
ans.extract(*pos ^ *nxt);
if (pos != a.begin() && nxt != a.end())
ans.insert(*prv ^ *nxt);
a.extract(x);
}else{
cout << *ans.begin() << '\n';
}
}
return 0;
}


Ex - Make Q (abc308 Ex)

题目大意

<++>

解题思路

<++>

神奇的代码


本文作者:~Lanly~

本文链接:https://www.cnblogs.com/Lanly/p/17520268.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ~Lanly~  阅读(613)  评论(2编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.