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 获胜等价于:

\[\begin{cases} p_1 \equiv p_2 \equiv p_3 \pmod 2\\ p_4\bmod 2 = 0 \end{cases}\]

于是考虑先将 \(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\) 时的胜利游戏状态数量,则有转移:

\[f_{i, j, k} = \max\left( f_{i - 1, j, k}, f_{i, j - 1, k}, f_{i, j, k - 1} \right) + \left[p_1 \equiv p_2 \equiv p_3 \pmod 2\right] \]

\(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,费用为相反数。

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

posted @ 2024-04-09 11:24  Luckyblock  阅读(459)  评论(0)    收藏  举报