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

枚举。

记录给定矩阵中每个数出现次数,选择其中最小值为 a1,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

贪心,枚举。

给定限制 n22.5×107,考虑枚举修改区间长度 len 并检查是否合法。

考虑贪心地进行修改,每次选择字符串中最左侧第一个 0,并以该位置为左端点进行一次修改,可以发现若 len 合法则这样一定构造出全 1 串。

然而直接暴力实现是 O(n2) 的,但是发现每次选择的 0 的位置一定是递增的,且一个位置在若干次修改后是否为 0 仅与其初始值与该位置被修改次数(即被区间覆盖次数)有关,于是考虑在顺序枚举位置并进行区间修改时,差分维护每个位置被修改次数即可判断某个位置是否需要被修改。

单次检查时间复杂度变为 O(n) 级别,总时间复杂度 O(n2) 级别。

//
/*
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

结论。

发现对于某个游戏状态 p1,p2,p3,p4,Bob 获胜等价于:

{p1p2p3(mod2)p4mod2=0

于是考虑先将 p4 调整为最接近的 2 的倍数,然后构造出所有仅考虑 p1,p2,p3 的 Bob 胜利游戏状态,然后再不断调整 p4 直至游戏结束。则答案即为 p1,p2,p3 的胜利游戏状态数 + p42

赛时一看数据范围这么小懒得想了,直接写了个三维 DP,记 fi,j,k 表示 p1=i,p2=j,p3=k 时的胜利游戏状态数量,则有转移:

fi,j,k=max(fi1,j,k,fi,j1,k,fi,j,k1)+[p1p2p3(mod2)]

O(2003) 地预处理后 O(1) 回答询问即可,答案即为 fp1,p2,p3+p42

然而进一步地考虑,实际上连预处理都不需要。可以发现一种最优的操作方案是先将 p1,p2,p3 均调整到最接近的 2 的倍数,然后依次将 p1,p2,p3 调整到 0,即仅关注 ijk0(mod2) 时的贡献。可以证明若通过调整使得 ijk1(mod2) 之后,若保持该性质并获取贡献,可得到的总贡献肯定不会多于上述方案;若再调整为 ijk0(mod2) 则贡献一定会更少。

注意若初始时 p1,p2,p3 即为合法方案,则需要令答案加 1。

综上,按照上述方案答案即为 p12+p22+p32+p42+[p1p2p3(mod2)]

以下为赛时代码。

//
/*
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)(n,m) 的路径上的数的公因数,等价于检查是否存在一条路径使所有数均可整除 d,从 (1,1) 开始 BFS 钦定只能经过可整除 d 的数,检查是否可以到达 (n,m) 即可。

发现答案必然为 gcd(a1,1,an,m) 的因数,由结论可知不大于 v 的数其因数数量不超过 O(v) 级别,于是直接枚举因数并大力检查即可。

总时间复杂度 O(nmv) 级别,其中 v106

//
/*
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,m50,则当攻击范围较扩大时伤害减怪物血量将很快衰减到负值,则实际上有贡献的攻击半径数量级相当小,应有 rlog3(n×m×maxp)12.7,考虑取最大半径为 R。又需要使得每座防御塔均有攻击半径,有一种满流的美感,考虑费用流。

具体地,建立源点 S 与汇点 T

  • 对于所有 k 座防御塔,与攻击半径 1R 分别建立节点。
  • 源点向所有防御塔连边,容量为 1,费用为 0。
  • 所有攻击半径向汇点连边,容量为 1,费用为 0。
  • 对于所有防御塔 i(1ik),枚举确定该防御塔攻击半径为 j(1jR) 时可覆盖路径位置数量 x,则从防御塔 i 向攻击半径 j 连边,容量为 1,费用为 max(0,x×pi3j)

建图后跑最小费用最大流,输出最小费用的相反数即为答案。

总点数为 O(k+R) 级别,总边数为 O(kR) 级别,通过建图方式可知最大流至多仅有 R,则总时间复杂度上界O(R2×k2),然而远远到不了上界,实际运行效率较高。

代码中取了 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,费用为相反数。

  1. https://www.cnblogs.com/ubospica/p/10392523.html ↩︎

posted @   Luckyblock  阅读(412)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示