2023-10-28 23:03阅读: 594评论: 0推荐: 0

AtCoder Beginner Contest 326

A - 2UP3DOWN (abc326 A)

题目大意

100楼层,一次可以上最多两层,或下最多三层。

给定两楼层,问能否一次到达。

解题思路

比较大小,然后判断其差是是不是在23内即可。

神奇的代码
#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 x, y;
cin >> x >> y;
if (x > y && x - y <= 3)
cout << "Yes" << '\n';
else if (x <= y && y - x <= 2)
cout << "Yes" << '\n';
else
cout << "No" << '\n';
return 0;
}


B - 326-like Numbers (abc326 B)

题目大意

给定一个n,问不小于 n的形如 326的数字是多少。

形如 326的数字,即数位有 3,且百位 ×十位 =个位。

解题思路

只有1000个数,枚举判断即可。

神奇的代码
#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;
auto is_326 = [](int x) {
auto s = to_string(x);
return (s[0] - '0') * (s[1] - '0') == (s[2] - '0');
};
while (!is_326(n))
++n;
cout << n << '\n';
return 0;
}


C - Peak (abc326 C)

题目大意

给定一维数轴上的n个礼物点,问长度为M的左闭右开的区间最多有多少个点。

解题思路

注意到最好情况下,其区间的左端点一定是礼物点。

因此我们可以枚举左端点所在的礼物点,然后看看到右端点时包含了多少个礼物。这个可以先对礼物点排序然后二分找到右端点礼物,作差得到数量。

当然也可以双指针。

ranges::的用法是c++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 n, m;
cin >> n >> m;
vector<int> a(n);
for (auto& i : a)
cin >> i;
ranges::sort(a);
int ans = 0;
for (int i = 0; i < n; ++i) {
ans = max(ans, int(ranges::lower_bound(a, a[i] + m) - a.begin() - i));
}
cout << ans << '\n';
return 0;
}


D - ABC Puzzle (abc326 D)

题目大意

给定两个仅包含ABC的长度为n的串a,b,要求构造一个n×n的矩阵,要求:

  • 每行每列包含恰好一个ABC
  • 每行最左边的字母组成字符串a
  • 每列最顶部的字母组成字符串a

解题思路

因为n只有 5,考虑朴素搜索。

每行ABC的排列情况只有5×4×3=60种, 5行的总情况为 605=7e9,但如果中间剪枝的话其合法情况数远远小于该数。

具体来说,考虑当前位置 (x,y)放什么,我们需要第 x行的放置情况 row[x],第 y列的放置情况 col[y],以决定该位置能否放该字母(代码里是二进制压缩的状态)。还需要 left[x]表示第 x行的是否放过字母,以决定是否和字符串 a比较,同理也需要 top[y]表示第 y列是否放过字母,以决定是否和字符串 b比较。这里的剪枝可以减去大部份合法情况。

在考虑完一行后,可以看看 row[x]是否放了ABC三个字母,没放满则直接剪枝。

感觉写的很复杂。

神奇的代码
#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;
string a, b;
cin >> n >> a >> b;
vector<vector<int>> ans(n, vector<int>(n, -1));
vector<int> top(n, 1);
vector<int> left(n, 1);
vector<int> row(n);
vector<int> col(n);
auto not_have = [&](int x, int y) { return ((~x >> y) & 1); };
function<bool(int, int)> dfs = [&](int x, int y) {
if (x == n) {
for (auto& i : row)
if (i != 7)
return false;
for (auto& i : col)
if (i != 7)
return false;
return true;
}
for (int i = 0; i < 3; ++i) {
if (not_have(row[x], i) && not_have(col[y], i)) {
if (top[y] && b[y] - 'A' != i)
continue;
if (left[x] && a[x] - 'A' != i)
continue;
row[x] |= (1 << i);
col[y] |= (1 << i);
ans[x][y] = i;
int ttop = top[y], lleft = left[x];
top[y] = 0;
left[x] = 0;
if (y == n - 1) {
if (row[x] == 7 && dfs(x + 1, 0))
return true;
} else {
if (dfs(x, y + 1))
return true;
}
row[x] ^= (1 << i);
col[y] ^= (1 << i);
ans[x][y] = -1;
top[y] = ttop;
left[x] = lleft;
}
}
if (y == n - 1) {
if (row[x] != 7)
return false;
if (dfs(x + 1, 0))
return true;
} else {
if (dfs(x, y + 1))
return true;
}
return false;
};
if (dfs(0, 0)) {
cout << "Yes" << '\n';
for (auto& i : ans) {
for (auto& j : i) {
if (j == -1)
cout << '.';
else
cout << char(j + 'A');
}
cout << '\n';
}
} else
cout << "No" << '\n';
return 0;
}


E - Revenge of "The Salary of AtCoder Inc." (abc326 E)

题目大意

给定包含n个数字的数组 a,以及一个等概率的 n面骰子。进行以下游戏:

初始x=0,投掷一次骰子,得到 y

  • 如果x<y,则获得 ay元,同时令 x=y
  • 否则游戏结束

问期望获得的钱数。

解题思路

期望题,根据定义,一个状态的期望,它是所有后继状态的期望的加权平均,这里的权重是概率。

当前状态是x,则设dp[x]表示当前掷出的值为 x,继续游戏至游戏结束,这期间获得的期望钱数。

很显然初始条件 dp[n]=0,即 x=n时,无论怎么骰,始终有 yn,游戏总是会结束。

考虑一般情况,当前状态是x,考虑投掷一次骰子的后继状态:

  • xn的概率投掷出yx,此时游戏结束,后继状态的期望收入为 0
  • 1n的概率投掷出y=x+1,此时进入后继状态dp[x+1],获得收益ax+1
  • 1n的概率投掷出y=x+2,此时进入后继状态dp[x+2],获得收益ax+2
  • 1n的概率投掷出y=x+3,此时进入后继状态dp[x+3],获得收益ax+3
  • ...
  • 1n的概率投掷出y=n,此时进入后继状态dp[n],获得收益an

由此可以写出转移式子:

dp[x]=xn×0+i=x+1n1n(dp[i]+a[i])=1ni=x+1n(dp[i]+a[i])

这是一个后缀和,求dp的时候维护一下就可以了。观察到dp数组和后缀和的关系,代码里直接把dp数组省掉了。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mo = 998244353;
long long qpower(long long a, long long b) {
long long qwq = 1;
while (b) {
if (b & 1)
qwq = qwq * a % mo;
a = a * a % mo;
b >>= 1;
}
return qwq;
}
long long inv(long long x) { return qpower(x, mo - 2); }
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;
int presum = 0, invn = inv(n);
for (int i = n - 1; i >= 0; --i) {
presum = (0ll + presum + 1ll * presum * invn % mo + a[i]) % mo;
}
cout << 1ll * presum * invn % mo << '\n';
return 0;
}


F - Robot Rotation (abc326 F)

题目大意

二维平面,机器人初始 (0,0),面向x轴正方向。给定n次移动的距离数组d,每次移动前,你需要指定机器人顺时针或逆时针转 90度,随后移动。

问能否移动到达目标点 (x,y),能移动到则给出一个可行的旋转序列。

解题思路

注意到n只有80,且每次必须旋转机器人,这意味着机器人有一半是上下移动,一半是左右移动,最多 40次。

横纵坐标的移动我们是可以分开考虑的,因此我们就分别考虑横坐标移动的 40次操作和纵坐标移动的 40次操作。

而操作,事实上就是决定移动距离的正负性。注意到 40其实是个很微妙的性质,它的一半 20是可以承受指数的复杂度的。即采用折半搜索的策略。

即对于这 40次操作,我们要对它们赋予正负号,看它们的和能否成为 目标移动距离x(或 y)。

我们可以分别对前20个数和后20,花 O(220)枚举它们的正负号,记录它们的和。

然后再把这两部份的结果合并:能否从左右两边的结果各挑一个数,它们的和=x。这是一个非常简单的子问题,对两边排序后一个双指针就可以解决了。这里的时间复杂度是 O(220log)

分别对x,y方向进行一次折半搜索,就可以搜到结果了。

由于要输出方案,因此我们得记录该结果的每一项的正负号,下面代码是通过二进制压缩的形式记录的,最后还要通过每一项的正负号,以及当前机器人的朝向决定向左向右转。

神奇的代码
#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, x, y;
cin >> n >> x >> y;
array<vector<int>, 2> a;
for (int i = 0; i < n; ++i) {
int x;
cin >> x;
a[i & 1].push_back(x);
}
auto dfs = [&](auto self, const vector<int>& a, int pos, int sum, int sol,
int l, int r, vector<array<int, 2>>& tmp) {
if (pos + l == r) {
tmp.push_back({sum, sol});
return;
}
self(self, a, pos + 1, sum + a[pos + l], sol, l, r, tmp);
self(self, a, pos + 1, sum - a[pos + l], (sol | (1 << pos)), l, r, tmp);
};
auto solve = [&](const vector<int>& a, int val) {
int mid = a.size() / 2;
vector<array<int, 2>> l, r;
dfs(dfs, a, 0, 0, 0, 0, mid, l);
dfs(dfs, a, 0, 0, 0, mid, a.size(), r);
vector<int> lid(l.size()), rid(r.size());
iota(lid.begin(), lid.end(), 0);
iota(rid.begin(), rid.end(), 0);
ranges::sort(lid, [&](int x, int y) { return l[x][0] < l[y][0]; });
ranges::sort(rid, [&](int x, int y) { return r[x][0] > r[y][0]; });
auto lp = lid.begin(), rp = rid.begin();
while (lp != lid.end() && rp != rid.end()) {
if (l[*lp][0] + r[*rp][0] == val) {
return l[*lp][1] + ((1ll * r[*rp][1]) << mid);
} else if (l[*lp][0] + r[*rp][0] < val) {
lp = next(lp);
} else if (l[*lp][0] + r[*rp][0] > val) {
rp = next(rp);
}
}
return -1ll;
};
auto dy = solve(a[0], y); // 0 => +, 1 => -
auto dx = solve(a[1], x);
if (dy == -1 || dx == -1)
cout << "No" << '\n';
else {
cout << "Yes" << '\n';
vector<int> ans;
for (int i = 0; i <= n / 2; ++i) {
ans.push_back((dy >> i) & 1);
if (i < n / 2)
ans.push_back((dx >> i) & 1);
}
int dir = 0;
for (int i = 0; i < n; ++i) {
cout << "LR"[ans[i] ^ dir ^ (i & 1)];
dir = ans[i];
}
cout << '\n';
}
return 0;
}


G - Unlock Achievement (abc326 G)

题目大意

给定n个技能和 m个成就。对于第 i个技能,升一级花费 ci

达成第 i个成就, 有ai的奖励,达成条件为,对于每个技能要达到指定等级或以上。

问最大的收益,即奖励花费的最大值。

解题思路

考虑只有一个技能和一个成就,那我们选择是否完成这个成就,就看其技能升级的成本成就的奖励的大小。

当考虑多个一个技能和多个成就时,成本奖励之间的衡量就没那么单纯:我可能完成了一个成就,而当我升级一次技能,又能立刻完成另一个成就——即不用从零等级技能考虑。这种有泛泛的,复杂关系决策的,网络流可能会比较在行。

成就只有达成未达成两种状态,而技能有5种状态,但如果根据完成成就的条件——某技能等级于x或以上,将技能拆分成这5种情况,那么这些情况同样也只有 达成未达成两种状态。

接下来要做的就是规定每个成就和每个技能状态,是达成不达成,其中会有一些代价,这种二分类问题的最小化代价,可以转换成最小割问题。而最小割的问题可以转换成最大流求解。

考虑一张图,有源点S和汇点T,其余点就是上述所说的状态,即每个成就表示成一个点,每个技能拆分成5个点,分别表示该技能等级达到i级或以上

一个会将这张图分成两部分,一部分与S相连,一部分与T相连。可以规定与S相连的点表示状态 未达成,与T相连的点表示达成

由于最小割是最小化代价,而成就奖励要最大化,这里需要转换一下,先假设完成了所有成就,那我们的决策就是决定成就是否不完成,即最小化未达成成就的代价

考虑如何连边以及边权(容量)设定,注意到网络流里,当一条边的容量满了,意味着这条边被割去了,即不与S(或 T)相连,换句话说边权就是不成为某一类的代价

对于成就这一状态,它不成为达成状态的代价就是a_i,即如果我选择不达成该成就,即与T的边断开,则我将失去其奖励 ai。因此每个成就与汇点T连一条容量为 ai的边。

对于 技能,它有5个状态,(以下等级1表示技能等级达到1或以上)除了等级1状态外,源点S与其他状态 连一条容量为ci的边,即升级一次的代价。比如 等级2状态,我花费ci升级一次至等级 2,那该状态就是 达成的,它不成为未达成这一状态了。

当然这里需要避免一种情况,就是等级3达成了,但等级2未达成。我们需要从等级2连一条边到等级3,其容量为无穷大。这样子,尽管S3...T的边割去了,还存在 S23...T 的路径,要割去这条路径,只能把S2也割去,这样子 等级2也达成了。因为23是无穷大的,所以这条边不会被割去,它只能割S2,因为其代价更小。这意味着技能等级之间的依赖关系不能被割去。

还有一种避免的情况就是,成就要求的技能等级都达成了,但成就未达成,这需要连一条从对应的技能状态该成就的容量为 无穷大的边,这意味着技能和成就之间的关系不能被割去。

建好图后,跑一遍最大流得最小割,用成就总奖励减去最小割即为答案。

神奇的代码
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
template <typename T> class flow_graph {
public:
static constexpr T eps = (T)1e-9;
struct edge {
int from;
int to;
T c;
T f;
};
vector<vector<int>> g;
vector<edge> edges;
int n;
int st;
int fin;
T flow;
flow_graph(int _n, int _st, int _fin) : n(_n), st(_st), fin(_fin) {
assert(0 <= st && st < n && 0 <= fin && fin < n && st != fin);
g.resize(n);
flow = 0;
}
void clear_flow() {
for (const edge& e : edges) {
e.f = 0;
}
flow = 0;
}
int add(int from, int to, T forward_cap, T backward_cap) {
assert(0 <= from && from < n && 0 <= to && to < n);
int id = (int)edges.size();
g[from].push_back(id);
edges.push_back({from, to, forward_cap, 0});
g[to].push_back(id + 1);
edges.push_back({to, from, backward_cap, 0});
return id;
}
};
template <typename T> class dinic {
public:
flow_graph<T>& g;
vector<int> ptr;
vector<int> d;
vector<int> q;
dinic(flow_graph<T>& _g) : g(_g) {
ptr.resize(g.n);
d.resize(g.n);
q.resize(g.n);
}
bool expath() {
fill(d.begin(), d.end(), -1);
q[0] = g.fin;
d[g.fin] = 0;
int beg = 0, end = 1;
while (beg < end) {
int i = q[beg++];
for (int id : g.g[i]) {
const auto& e = g.edges[id];
const auto& back = g.edges[id ^ 1];
if (back.c - back.f > g.eps && d[e.to] == -1) {
d[e.to] = d[i] + 1;
if (e.to == g.st) {
return true;
}
q[end++] = e.to;
}
}
}
return false;
}
T dfs(int v, T w) {
if (v == g.fin) {
return w;
}
int& j = ptr[v];
while (j >= 0) {
int id = g.g[v][j];
const auto& e = g.edges[id];
if (e.c - e.f > g.eps && d[e.to] == d[v] - 1) {
T t = dfs(e.to, min(e.c - e.f, w));
if (t > g.eps) {
g.edges[id].f += t;
g.edges[id ^ 1].f -= t;
return t;
}
}
j--;
}
return 0;
}
T max_flow() {
while (expath()) {
for (int i = 0; i < g.n; i++) {
ptr[i] = (int)g.g[i].size() - 1;
}
T big_add = 0;
while (true) {
T add = dfs(g.st, numeric_limits<T>::max());
if (add <= g.eps) {
break;
}
big_add += add;
}
if (big_add <= g.eps) {
break;
}
g.flow += big_add;
}
return g.flow;
}
vector<int> min_cut() {
max_flow();
vector<int> ret(g.n);
for (int i = 0; i < g.n; i++) {
ret[i] = (d[i] != -1);
}
return ret;
}
};
const int inf = 1e9 + 7;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n, m;
cin >> n >> m;
vector<int> c(n), a(m);
for (auto& i : c)
cin >> i;
for (auto& i : a)
cin >> i;
int st = 0, pst = st + 1, ast = pst + n * 5, ed = ast + m, num = ed + 1;
flow_graph<int> g(num, st, ed);
for (int i = 0; i < n; ++i) {
int l = i * 5, r = l + 5;
for (int j = l; j < r; ++j) {
int pj = pst + j;
if (j == l) {
g.add(pj, ed, inf, 0);
} else {
g.add(st, pj, c[i], 0);
g.add(pj - 1, pj, inf, 0);
}
}
}
for (int i = 0; i < m; ++i) {
int ai = ast + i;
g.add(ai, ed, a[i], 0);
}
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j) {
int x;
cin >> x;
--x;
int ai = ast + i, pj = pst + j * 5 + x;
g.add(pj, ai, inf, 0);
}
dinic solver(g);
int ans = accumulate(a.begin(), a.end(), 0) - solver.max_flow();
cout << ans << '\n';
return 0;
}


本文作者:~Lanly~

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

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

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