Codeforces Round 938 (Div. 3)
写在前面
比赛地址:https://codeforces.com/contest/1955。
练手速的。
唉状态太垃圾了纯唐氏一个
A
签到。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
//=============================================================
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
int n, a, b; std::cin >> n >> a >> b;
if (b >= 2 * a) {
std::cout << n * a << "\n";
} else {
int k = n / 2, r = n % 2;
std::cout << k * b + r * a << "\n";
}
}
return 0;
}
B
枚举。
记录给定矩阵中每个数出现次数,选择其中最小值为 \(a_{1, 1}\),构造出合法矩阵的唯一形态并检查每个数出现次数是否合法即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 510;
//=============================================================
//=============================================================
std::map <LL, int> cnt;
int a[kN][kN];
std::vector <int> b;
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
int n, c, d; std::cin >> n >> c >> d;
cnt.clear();
b.clear();
for (int i = 1; i <= n * n; ++ i) {
int x; std::cin >> x;
cnt[x] = cnt[x] + 1;
b.push_back(x);
}
std::sort(b.begin(), b.end());
int a11 = b[0], flag = 1;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= n; ++ j) {
LL x = a11 + 1ll * (i - 1) * c + 1ll * (j - 1) * d;
if (!cnt.count(x) || cnt[x] == 0) flag = 0;
cnt[x] = cnt[x] - 1;
}
}
std::cout << (flag ? "YES\n" : "NO\n");
}
return 0;
}
C
模拟。
模拟地每次将左右两端血量较少的一个消灭即可。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, a[kN];
LL k;
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
std::cin >> k;
for (int i = 1; i <= n; ++ i) std::cin >> a[i];
int p[2] = {1, n}, now = 0, ans = 0;
while (k && p[0] <= p[1]) {
LL need = 0;
if (p[0] == p[1]) need = a[p[0]];
else if (a[p[now]] <= a[p[now ^ 1]]) need = 2ll * a[p[now]] - 1;
else need = 2ll * a[p[now ^ 1]];
if (need > k) break;
k -= need, ++ ans;
if (p[0] == p[1]) break;
else if (a[p[now]] <= a[p[now ^ 1]]) {
a[p[now ^ 1]] -= a[p[now]] - 1, a[p[now]] = 0;
if (now == 0) ++ p[0];
else -- p[1];
now = now ^ 1;
} else {
a[p[now]] -= a[p[now ^ 1]], a[p[now ^ 1]] = 0;
if (now == 0) -- p[1];
else ++ p[0];
}
}
std::cout << ans << "\n";
}
return 0;
}
/*
1
2 5
5 2
*/
D
枚举。
记录每个数在 \(b\) 中的出现次数。
顺序枚举 \(a\) 中所有长度为 \(m\) 的区间,同时维护其中有多少在 \(b\) 中的数,若大于 \(k\) 则该区间有贡献。
注意若某个数在区间中出现次数大于其在 \(b\) 中出现次数,则超出部分无贡献。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n, m, k, cnt, ans, a[kN], b[kN];
int cnta[kN], cntb[kN];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> m >> k;
cnt = ans = 0;
for (int i = 1; i <= n; ++ i) {
std::cin >> a[i];
cnta[a[i]] = cntb[a[i]] = 0;
}
for (int i = 1; i <= m; ++ i) {
std::cin >> b[i];
++ cntb[b[i]];
}
for (int i = 1; i <= m; ++ i) {
if (cntb[a[i]] && cnta[a[i]] < cntb[a[i]]) ++ cnt;
++ cnta[a[i]];
}
for (int l = 1, r = m; r <= n; ++ l, ++ r) {
if (cnt >= k) ++ ans;
if (r == n) break;
-- cnta[a[l]];
if (cntb[a[l]] && cnta[a[l]] < cntb[a[l]]) -- cnt;
if (cntb[a[r + 1]] && cnta[a[r + 1]] < cntb[a[r + 1]]) ++ cnt;
++ cnta[a[r + 1]];
}
std::cout << ans << "\n";
}
return 0;
}
E
贪心,枚举。
给定限制 \(n^2\le 2.5\times 10^7\),考虑枚举修改区间长度 \(\operatorname{len}\) 并检查是否合法。
考虑贪心地进行修改,每次选择字符串中最左侧第一个 0,并以该位置为左端点进行一次修改,可以发现若 \(\operatorname{len}\) 合法则这样一定构造出全 1 串。
然而直接暴力实现是 \(O(n^2)\) 的,但是发现每次选择的 0 的位置一定是递增的,且一个位置在若干次修改后是否为 0 仅与其初始值与该位置被修改次数(即被区间覆盖次数)有关,于是考虑在顺序枚举位置并进行区间修改时,差分维护每个位置被修改次数即可判断某个位置是否需要被修改。
单次检查时间复杂度变为 \(O(n)\) 级别,总时间复杂度 \(O(n^2)\) 级别。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5010;
//=============================================================
int n, d[kN];
std::string s, t;
//=============================================================
bool check(int len_) {
t = s;
for (int i = 0; i < n; ++ i) d[i] = 0;
for (int i = 0; i < n; ++ i) {
d[i] += d[i - 1];
int ch = s[i] - '0';
if (d[i] % 2 == 1) ch ^= 1;
if (ch == 0) {
if (i <= n - len_) ++ d[i], -- d[i + len_];
else return false;
}
}
return true;
}
//=============================================================
int main() {
//freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n;
std::cin >> s;
for (int i = n; i; -- i) {
if (check(i)) {
std::cout << i << "\n";
break;
}
}
}
return 0;
}
F
结论。
发现对于某个游戏状态 \(p_1, p_2, p_3, p_4\),Bob 获胜等价于:
于是考虑先将 \(p_4\) 调整为最接近的 2 的倍数,然后构造出所有仅考虑 \(p_1, p_2, p_3\) 的 Bob 胜利游戏状态,然后再不断调整 \(p_4\) 直至游戏结束。则答案即为 \(p_1, p_2, p_3\) 的胜利游戏状态数 + \(\left\lfloor\frac{p_4}{2}\right\rfloor\)。
赛时一看数据范围这么小懒得想了,直接写了个三维 DP,记 \(f_{i, j, k}\) 表示 \(p_1 = i, p_2 = j, p_3 = k\) 时的胜利游戏状态数量,则有转移:
\(O(200^3)\) 地预处理后 \(O(1)\) 回答询问即可,答案即为 \(f_{p_1, p_2, p_3} + \left\lfloor\frac{p_4}{2}\right\rfloor\)。
然而进一步地考虑,实际上连预处理都不需要。可以发现一种最优的操作方案是先将 \(p_1, p_2, p_3\) 均调整到最接近的 2 的倍数,然后依次将 \(p_1, p_2, p_3\) 调整到 0,即仅关注 \(i \equiv j \equiv k \equiv 0 \pmod 2\) 时的贡献。可以证明若通过调整使得 \(i \equiv j \equiv k \equiv 1 \pmod 2\) 之后,若保持该性质并获取贡献,可得到的总贡献肯定不会多于上述方案;若再调整为 \(i \equiv j \equiv k \equiv 0 \pmod 2\) 则贡献一定会更少。
注意若初始时 \(p_1, p_2, p_3\) 即为合法方案,则需要令答案加 1。
综上,按照上述方案答案即为 \(\left\lfloor\frac{p_1}{2}\right\rfloor + \left\lfloor\frac{p_2}{2}\right\rfloor + \left\lfloor\frac{p_3}{2}\right\rfloor + \left\lfloor\frac{p_4}{2}\right\rfloor + \left[p_1 \equiv p_2 \equiv p_3 \pmod 2\right]\)。
以下为赛时代码。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 210;
//=============================================================
LL f[kN][kN][kN];
//=============================================================
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
for (int i = 0; i <= 200; ++ i) {
for (int j = 0; j <= 200; ++ j) {
for (int k = 0; k <= 200; ++ k) {
int d = (((i + 1) % 2 + (j % 2)) == (2 * (k % 2)));
f[i + 1][j][k] = std::max(f[i + 1][j][k], f[i][j][k] + d);
d = (((i % 2) + ((j + 1) % 2)) == (2 * (k % 2)));
f[i][j + 1][k] = std::max(f[i][j + 1][k], f[i][j][k] + d);
d = (((i % 2) + (j % 2)) == (2 * ((k + 1) % 2)));
f[i][j][k + 1] = std::max(f[i][j][k + 1], f[i][j][k] + d);
}
}
}
int T; std::cin >> T;
while (T --) {
int p[4];
for (int i = 0; i < 4; ++ i) std::cin >> p[i];
// LL ans = p[4] / 2;
std::cout << (f[p[0]][p[1]][p[2]] + (p[3] / 2)) << "\n";
}
return 0;
}
G
枚举,数论。
典中典之枚举 \(\gcd\)。
考虑如何检查一个数 \(d\) 能否成为从 \((1, 1)\rightarrow (n, m)\) 的路径上的数的公因数,等价于检查是否存在一条路径使所有数均可整除 \(d\),从 \((1, 1)\) 开始 BFS 钦定只能经过可整除 \(d\) 的数,检查是否可以到达 \((n, m)\) 即可。
发现答案必然为 \(\gcd(a_{1, 1}, a_{n, m})\) 的因数,由结论[1]可知不大于 \(v\) 的数其因数数量不超过 \(O\left(\sqrt v\right)\) 级别,于是直接枚举因数并大力检查即可。
总时间复杂度 \(O\left(\sum nm\sqrt v\right)\) 级别,其中 \(v \le 10^6\)。
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kN = 110;
const int kM = 1e6;
//=============================================================
int n, m, a[kN][kN];
bool vis[kN][kN];
//=============================================================
int gcd(int x_, int y_) {
return y_ ? gcd(y_, x_ % y_) : x_;
}
bool check(int prod_) {
std::queue <pii> q;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
vis[i][j] = 0;
}
}
q.push(mp(1, 1));
while (!q.empty()) {
pii u = q.front(); q.pop();
int x = u.first, y = u.second;
if (vis[x][y]) continue;
vis[x][y] = 1;
if (x + 1 <= n && a[x + 1][y] % prod_ == 0) {
if (!vis[x + 1][y]) q.push(mp(x + 1, y));
}
if (y + 1 <= m && a[x][y + 1] % prod_ == 0) {
if (!vis[x][y + 1]) q.push(mp(x, y + 1));
}
}
return vis[n][m];
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
std::ios::sync_with_stdio(0), std::cin.tie(0);
int T; std::cin >> T;
while (T --) {
std::cin >> n >> m;
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
std::cin >> a[i][j];
}
}
int lim = gcd(a[1][1], a[n][m]), ans = 1;
for (int i = 1; i * i <= lim; ++ i) {
if (lim % i) continue;
if ((lim / i) > ans && check(lim / i)) ans = lim / i;
if (i > ans && check(i)) ans = i;
}
std::cout << ans << "\n";
}
return 0;
}
H
费用流。
要是没有防御塔必须攻击半径不同的限制将使纯恼弹题,然而有,则需要考虑如何分配攻击半径。
指数增长数量级太快了,并且 \(n,m\le 50\),则当攻击范围较扩大时伤害减怪物血量将很快衰减到负值,则实际上有贡献的攻击半径数量级相当小,应有 \(r\le \log_3 (n\times m\times \max p) \approx 12.7\),考虑取最大半径为 \(R\)。又需要使得每座防御塔均有攻击半径,有一种满流的美感,考虑费用流。
具体地,建立源点 \(S\) 与汇点 \(T\):
- 对于所有 \(k\) 座防御塔,与攻击半径 \(1\sim R\) 分别建立节点。
- 源点向所有防御塔连边,容量为 1,费用为 0。
- 所有攻击半径向汇点连边,容量为 1,费用为 0。
- 对于所有防御塔 \(i(1\le i\le k)\),枚举确定该防御塔攻击半径为 \(j(1\le j\le R)\) 时可覆盖路径位置数量 \(x\),则从防御塔 \(i\) 向攻击半径 \(j\) 连边,容量为 1,费用为 \(-\max(0, x\times p_i - 3^j)\)。
建图后跑最小费用最大流,输出最小费用的相反数即为答案。
总点数为 \(O(k + R)\) 级别,总边数为 \(O(kR)\) 级别,通过建图方式可知最大流至多仅有 \(R\),则总时间复杂度上界为 \(O(R^2\times k^2)\),然而远远到不了上界,实际运行效率较高。
代码中取了 \(R=15\)。
同样考虑如何分配攻击半径,也可以状压 DP 解决此题,详见其他题解。
//By:Luckyblock
/*
*/
#include <bits/stdc++.h>
#define LL long long
#define pii std::pair<int,int>
#define mp std::make_pair
const int kMap = 60;
const int kN = 2e5 + 10;
const int kM = 2e6 + 10;
const int kR = 15;
const LL kInf = 1e18 + 2077;
//=============================================================
int n, m, k, s, t;
char map[kMap];
std::vector <pii> road;
int nodenum, edgenum = 1, head[kN], v[kM], ne[kM];
LL w[kM], c[kM], dis[kN], maxflow, mincost;
int cur[kN];
bool vis[kN];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
return f * w;
}
void Add(int u_, int v_, LL w_, LL c_) {
v[++ edgenum] = v_;
w[edgenum] = w_;
c[edgenum] = c_;
ne[edgenum] = head[u_];
head[u_] = edgenum;
v[++ edgenum] = u_;
w[edgenum] = 0;
c[edgenum] = -c_;
ne[edgenum] = head[v_];
head[v_] = edgenum;
}
bool Spfa() {
std::queue <int> q;
for (int i = 0; i <= t; ++ i) dis[i] = kInf;
dis[s] = 0, vis[s] = 1;
q.push(s);
while (!q.empty()) {
int u_ = q.front(); q.pop();
vis[u_] = 0;
for (int i = head[u_]; i; i = ne[i]) {
int v_ = v[i]; LL w_ = w[i], c_ = c[i];
if (w_ > 0 && dis[u_] + c_ < dis[v_]) {
dis[v_] = dis[u_] + c_;
if (!vis[v_]) q.push(v_), vis[v_] = 1;
}
}
}
return dis[t] != kInf;
}
LL Dfs(int u_, LL into_) {
if (u_ == t || into_ == 0) return into_;
vis[u_] = 1;
LL out = 0;
for (int &i = cur[u_]; i && into_; i = ne[i]) {
int v_ = v[i]; LL w_ = w[i], c_ = c[i];
if (!vis[v_] && dis[v_] == dis[u_] + c_ && w_ > 0) {
LL ret = Dfs(v_, std::min(into_ - out, w_));
if (ret) {
w[i] -= ret, w[i ^ 1] += ret;
out += ret, into_ -= ret;
mincost += c_ * ret;
}
}
}
vis[u_] = 0;
return out;
}
LL MCMF() {
LL ret = 0, x;
while (Spfa()) {
for (int i = 0; i <= t; ++ i) cur[i] = head[i];
while ((x = Dfs(s, kInf))) ret += x;
}
return ret;
}
double distance(int x0_, int y0_, int x1_, int y1_) {
return 1.0 * (x0_ - x1_) * (x0_ - x1_) + 1.0 * (y0_ - y1_) * (y0_ - y1_);
}
void Init() {
for (int i = 0; i <= t; ++ i) head[i] = 0;
edgenum = 1;
mincost = maxflow = 0;
road.clear();
n = read(), m = read(), k = read();
for (int i = 1; i <= n; ++ i) {
scanf("%s", map + 1);
for (int j = 1; j <= m; ++ j) {
if (map[j] == '#') road.push_back(mp(i, j));
}
}
s = 0, t = k + kR + 10;
for (int i = 1; i <= kR; ++ i) {
Add(k + i, t, 1, 0);
}
for (int i = 1; i <= k; ++ i) {
int x = read(), y = read(), p = read();
std::vector <double> dis;
for (auto pos: road) {
dis.push_back(distance(x, y, pos.first, pos.second));
}
std::sort(dis.begin(), dis.end());
int pos = 0, sz = dis.size();
LL pow3 = 3, sum = 0;
Add(s, i, 1, 0);
for (int r = 1; r <= kR; ++ r) {
while (pos < sz && r * r >= dis[pos]) {
sum += p, ++ pos;
}
Add(i, k + r, 1, -std::max(0ll, sum - pow3));
pow3 *= 3ll;
}
}
}
//=============================================================
int main() {
// freopen("1.txt", "r", stdin);
int T = read();
while (T --) {
Init();
maxflow = MCMF();
printf("%lld\n", -mincost);
}
return 0;
}
/*
1
2 2 1
#.
##
1 2 1
1
3 3 2
#..
##.
.##
1 2 4
3 1 3
*/
写在最后
学到了什么:
- 典中典。
- H:注意建反边,令容量为 0,费用为相反数。

浙公网安备 33010602011771号