Hitomi's Note

瞳の笔记

Educational Codeforces Round 112 (Rated for Div. 2) [CF-1555]

发布于|# ACM# CodeForces

A - PizzaForces

PizzaForces is Petya’s favorite pizzeria. PizzaForces makes and sells pizzas of three sizes: small pizzas consist of 66 slices, medium ones consist of 88 slices, and large pizzas consist of 1010 slices each. Baking them takes 1515, 2020 and 2525 minutes, respectively.

Petya’s birthday is today, and nn of his friends will come, so he decided to make an order from his favorite pizzeria. Petya wants to order so much pizza that each of his friends gets at least one slice of pizza. The cooking time of the order is the total baking time of all the pizzas in the order.

Your task is to determine the minimum number of minutes that is needed to make pizzas containing at least nn slices in total. For example:

  • if 1212 friends come to Petya’s birthday, he has to order pizzas containing at least 1212 slices in total. He can order two small pizzas, containing exactly 1212 slices, and the time to bake them is 3030 minutes;
  • if 1515 friends come to Petya’s birthday, he has to order pizzas containing at least 1515 slices in total. He can order a small pizza and a large pizza, containing 1616 slices, and the time to bake them is 4040 minutes;
  • if 300300 friends come to Petya’s birthday, he has to order pizzas containing at least 300300 slices in total. He can order 1515 small pizzas, 1010 medium pizzas and 13 large pizzas, in total they contain 15⋅6+10⋅8+13⋅10=30015\cdot6+10\cdot8+13\cdot10=300 slices, and the total time to bake them is 15⋅15+10⋅20+13⋅25=75015\cdot15+10\cdot20+13\cdot25=750 minutes;
  • if only one friend comes to Petya’s birthday, he can order a small pizza, and the time to bake it is 1515 minutes.

本来应该是用动态规划,但是由于是 A 题。经过仔细阅读不难发现,每一块所需的时间都是 2.52.5,所以问的是组合数大于等于 nn 的最小值。

原数为 66、88、1010,同除以 22 可以得到 33、44、55。

证明以下:设有数 mm,若 m(mod3)=1m \pmod3=1,则必可以得到 m=3×a+4×bm=3\times a+4\times b。

若 m(mod3)=2m \pmod3=2,则必可以得到 m=3×a+5×bm=3\times a+5\times b。

若 m(mod3)=0m \pmod3=0,则必可以得到 m=3×am=3\times a。

所以可以得到,若 m≥3m\ge3 必然可以得到 m=3×a+4×b+5×cm=3\times a+4\times b+5\times c。

回到题目,若 n≥6n\ge6,若 nn 为偶数,则必然可以由 66、88、1010 相加得到,若为奇数则不行。

所以对 nn 进行上取余,不足当作 11。

#include <iostream>

using namespace std;

int main() {
  int t;
  cin >> t;
  while (t--) {
    long long n;
    cin >> n;
    cout << max(6LL, n + 1) / 2 * 5 << '\n';
  }
}
:hourglass::open_file_folder::bulb:
624.2朴素

这里再放一个动态规划的算法。66、88、1010 的最大公倍数为 120120。所以如果 n>2×120n>2\times 120 则必定可以化简为 120×2×n+n(mod120×2)120\times2\times n+n\pmod {120\times2}。在 n>2×120n>2\times120 的部分使用贪心,在 n<=2×120n<=2\times120 的部分使用动态规划。因为数据范围小,动态规划使用提前打表的形式。

#include <iostream>
#include <vector>

using namespace std;

int main() {

  int step;
  cin >> step;
  int lcm = 2 * 2 * 2 * 3 * 5;
  vector<int> dp(2 * lcm);
  for (int i = 1; i < 2 * lcm; i++) {
    int v1 = (i - 6) >= 0 ? dp[i - 6] : 0;
    int v2 = (i - 8) >= 0 ? dp[i - 8] : 0;
    int v3 = (i - 10) >= 0 ? dp[i - 10] : 0;
    dp[i] = min({v1 + 15, v2 + 20, v3 + 25});
  }
  while (step--) {
    long long n;
    cin >> n;
    if (n / lcm == 0) {
      cout << dp[n % lcm] << endl;
    } else {
      cout << (1ll * dp[lcm - 1] * (n / lcm - 1) + dp[n % lcm + lcm]) << endl;
    }
  }
}
:hourglass::open_file_folder::bulb:
624.2动态规划

B - Two Tables

You have an axis-aligned rectangle room with width WW and height HH, so the lower left corner is in point (0,0)(0,0) and the upper right corner is in (W,H)(W,H).

There is a rectangular table standing in this room. The sides of the table are parallel to the walls, the lower left corner is in (x1,y1)(x_1,y_1), and the upper right corner in (x2,y2)(x_2,y_2).

You want to place another rectangular table in this room with width ww and height hh with the width of the table parallel to the width of the room.

The problem is that sometimes there is not enough space to place the second table without intersecting with the first one (there are no problems with tables touching, though).

You can’t rotate any of the tables, but you can move the first table inside the room.

img

Example of how you may move the first table.

What is the minimum distance you should move the first table to free enough space for the second one?

题目给定一个矩形,可以移动改矩形,使得能够放得下另一个。

易见,最简单的方法就是上下或者左右移动,使得能够放下。

思路就很简单:上下能否放下   ⟹  \implies 计算上下移动所需的最小距离   ⟹  \implies 左右能否放下   ⟹  \implies 计算左右移动所需的最小距离

#include <iomanip>
#include <iostream>

using namespace std;

const int INF = int(1e9);

int main() {
  cout << fixed << setprecision(9);
  int t;
  cin >> t;
  while (t--) {
    int W, H;
    int x1, y1, x2, y2;
    int w, h;
    cin >> W >> H;
    cin >> x1 >> y1 >> x2 >> y2;
    cin >> w >> h;
    int ans = INF;
    if (x2 - x1 + w <= W) {
      ans = min(ans, max(0, w - x1));
      ans = min(ans, max(0, x2 - (W - w)));
    }
    if (y2 - y1 + h <= H) {
      ans = min(ans, max(0, h - y1));
      ans = min(ans, max(0, y2 - (H - h)));
    }
    if (ans == INF)
      cout << -1 << endl;
    else
      cout << double(ans) << endl;
  }
  return 0;
}
:hourglass::open_file_folder::bulb:
784.2朴素

C - Coin Rows

Alice and Bob are playing a game on a matrix, consisting of 22 rows and mm columns. The cell in the ii-th row in the jj-th column contains ai,ja_{i,j} coins in it.

Initially, both Alice and Bob are standing in a cell (1,1)(1,1). They are going to perform a sequence of moves to reach a cell (2,m)(2,m).

The possible moves are:

  • Move right — from some cell (x,y)(x,y) to (x,y+1)(x,y+1);
  • Move down — from some cell (x,y)(x,y) to (x+1,y)(x+1,y).

First, Alice makes all her moves until she reaches (2,m)(2,m). She collects the coins in all cells she visit (including the starting cell).

When Alice finishes, Bob starts his journey. He also performs the moves to reach (2,m)(2,m) and collects the coins in all cells that he visited, but Alice didn’t.

The score of the game is the total number of coins Bob collects.

Alice wants to minimize the score. Bob wants to maximize the score. What will the score of the game be if both players play optimally?

img

题目要求 Alice 先选,使得 Bob 能获得的金额尽量小。然后 Bob 再选,使得获得的金额最大。其中 Alice 选中的金额 Bob 中不计入。

img

仔细研究路线可以发现,Bob 要不就收集下方的金额,要不就收集上方的金额。

所以目标是研究 Alice 发生转角的地方。Alice 转向后,有上层转向下层。所以可以通过一个指针来确定和的最小值。

发生转角后,Bob 的值就成为了上排减去 Alice 经过值和下排减去 Alice 经过值的最大值。

思路就很简单:寻找 Alice 的转折处   ⟹  \implies 转折处上方剩余和下方剩余的最大值(即 Bob 获得的值)   ⟹  \implies 收集每次的值取最小

#include <iostream>
#include <vector>

using namespace std;

const int INF = 2e9 + 10;

int main() {
  int t;
  cin >> t;
  for (int _ = 0; _ < t; ++_) {
    int n;
    cin >> n;
    vector<vector<int>> a(2, vector<int>(n));
    for (int i = 0; i < 2; ++i) {
      for (int j = 0; j < n; ++j) {
        cin >> a[i][j];
      }
    }
    int ans = INF;
    int sum1 = 0, sum2 = 0;
    for (int i = 0; i < n; ++i) {
      sum1 += a[0][i];
    }
    for (int i = 0; i < n; ++i) {
      sum1 -= a[0][i];
      ans = min(ans, max(sum1, sum2));
      sum2 += a[1][i];
    }
    cout << ans << endl;
  }
}
:hourglass::open_file_folder::bulb:
2495.3贪心

D - Say No to Palindromes

Let’s call the string beautiful if it does not contain a substring of length at least 22, which is a palindrome. Recall that a palindrome is a string that reads the same way from the first character to the last and from the last character to the first. For example, the strings a, bab\texttt{bab}, acca\texttt{acca}, bcabcbacb\texttt{bcabcbacb} are palindromes, but the strings ab\texttt{ab}, abbbaa\texttt{abbbaa}, cccb\texttt{cccb} are not.

Let’s define cost of a string as the minimum number of operations so that the string becomes beautiful, if in one operation it is allowed to change any character of the string to one of the first 33 letters of the Latin alphabet (in lowercase).

You are given a string ss of length nn, each character of the string is one of the first 33 letters of the Latin alphabet (in lowercase).

You have to answer mm queries — calculate the cost of the substring of the string ss from lil_i-th to rir_i-th position, inclusive.

题目要求修改字符串使得原字符串不包含回文字符串。

经过简单的尝试,使用 abc, acb, bac, bca, cab, cba 作为循环节的字符串才符合要求。如果具有其它字符,则必定会包含回文。

因为不同要求均对同一段字符串进行处理,所以可以先对字符串进行预处理。

预处理的时候会进行字符统计。将字符划分成三个一段,统计如果以 abc, acb, bac, bca, cab, cba 作为循环节不符合的段数。在计算时就可以使用尾减去首来判断当前缺少或重复的字符个数。修改这些字符即可完成要求。

如果左右处于某一段的中间,由于循环节循环,所以已经遍历,不存在漏算。

注:如果使用快速读取技巧时间可以缩短到十分之一。

#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

int main() {
  int n, m;
  cin >> n >> m;
  string s;
  cin >> s;
  vector<vector<int>> pr(6, vector<int>(n + 1));
  string t = "abc";
  int cur = 0;
  do {
    for (int i = 0; i < n; ++i)
      pr[cur][i + 1] = pr[cur][i] + (s[i] != t[i % 3]);
    ++cur;
  } while (next_permutation(t.begin(), t.end()));
  while (m--) {
    int l, r;
    cin >> l >> r;
    int ans = n;
    for (int i = 0; i < 6; ++i)
      ans = min(ans, pr[i][r] - pr[i][l - 1]);
    cout << ans << endl;
  }
}
:hourglass::open_file_folder::bulb:
110710.2前缀和

E - Boring Segments

You are given nn segments on a number line, numbered from 11 to nn. The ii-th segments covers all integer points from lil_i to rir_i and has a value wiw_i.

You are asked to select a subset of these segments (possibly, all of them). Once the subset is selected, it’s possible to travel between two integer points if there exists a selected segment that covers both of them. A subset is good if it’s possible to reach point mm starting from point 11 in arbitrary number of moves.

The cost of the subset is the difference between the maximum and the minimum values of segments in it. Find the minimum cost of a good subset.

In every test there exists at least one good subset.

题目要求寻找一个长度至少为 mm 的区间,使得区间内的最大值与最小值之差最小。

典型的线段树,这里使用延迟更新的线段树来降低时间复杂度。

#include <algorithm>
#include <iostream>
#define lc(x) (x) << 1
#define rc(x) (x) << 1 | 1

using namespace std;

const int maxn = 300005, maxt = 4000005;
int n, m, ans;
int minn[maxt], lazy[maxt];
struct Segment {
  int l, r, v;
} s[maxn];
inline int cmp(Segment a, Segment b) { return a.v < b.v; }
inline void get_lazy(int now, int v) { lazy[now] += v, minn[now] += v; }
inline void push_down(int now) {
  if (lazy[now])
    get_lazy(lc(now), lazy[now]), get_lazy(rc(now), lazy[now]), lazy[now] = 0;
}
void update(int l, int r, int now, int L, int R, int v) {
  if (L <= l && r <= R) {
    get_lazy(now, v);
    return;
  }
  int mid = (l + r) >> 1;
  push_down(now);
  if (L <= mid)
    update(l, mid, lc(now), L, R, v);
  if (mid < R)
    update(mid + 1, r, rc(now), L, R, v);
  minn[now] = min(minn[lc(now)], minn[rc(now)]);
}
int main() {
  cin >> n >> m;
  for (int i = 1; i <= n; i++)
    cin >> s[i].l >> s[i].r >> s[i].v;
  sort(s + 1, s + 1 + n, cmp);
  ans = s[n].v;
  int l = 1, r = 0;
  while (r < n) {
    r++, update(1, m - 1, 1, s[r].l, s[r].r - 1, 1);
    while (l <= r && minn[1] > 0) {
      ans = min(ans, s[r].v - s[l].v);
      update(1, m - 1, 1, s[l].l, s[l].r - 1, -1), l++;
    }
  }
  cout << ans << endl;
  return 0;
}
:hourglass::open_file_folder::bulb:
149739.0尺取 + 线段树

F - Good Graph

You have an undirected graph consisting of nn vertices with weighted edges.

A simple cycle is a cycle of the graph without repeated vertices. Let the weight of the cycle be the XOR of weights of edges it consists of.

Let’s say the graph is good if all its simple cycles have weight 11. A graph is bad if it’s not good.

Initially, the graph is empty. Then qq queries follow. Each query has the next type:

  • uu vv xx — add edge between vertices uu and vv of weight xx if it doesn’t make the graph bad.

For each query print, was the edge added or not.

题解采用了 LCT 圆方树。看提交中有些靠着深搜优化外加读取加速也能过,但是一旦关闭读取加速就会导致超时。

#include <algorithm>
#include <array>
#include <iomanip>
#include <iostream>
#include <numeric>
#include <vector>

using namespace std;

#define fore(i, l, r) for (int(i) = int(l); (i) < int(r); (i)++)
#define sz(a) int((a).size())

#define x first
#define y second

typedef long long li;
typedef pair<int, int> pt;

template <class A, class B>
ostream &operator<<(ostream &out, const pair<A, B> &p) {
  return out << "(" << p.x << ", " << p.y << ")";
}
template <class A> ostream &operator<<(ostream &out, const vector<A> &v) {
  fore(i, 0, sz(v)) {
    if (i)
      out << " ";
    out << v[i];
  }
  return out;
}

const int INF = int(1e9);
const li INF64 = li(1e18);

int n, q;
vector<array<int, 3>> es;

inline bool read() {
  if (!(cin >> n >> q))
    return false;
  es.resize(q);
  fore(i, 0, q) {
    cin >> es[i][0] >> es[i][1] >> es[i][2];
    es[i][0]--;
    es[i][1]--;
  }
  return true;
}

vector<vector<pt>> g;

const int LOG = 19;
vector<int> up[LOG];
vector<int> tin, tout;
vector<int> xr;
int T = 0;

void dfs(int v, int p, int curXor) {
  tin[v] = T++;
  xr[v] = curXor;
  up[0][v] = p;
  fore(pw, 1, LOG) up[pw][v] = up[pw - 1][up[pw - 1][v]];

  for (auto [to, w] : g[v]) {
    if (to == p)
      continue;
    dfs(to, v, curXor ^ w);
  }
  tout[v] = T;
}

void buildLCA() {
  fore(pw, 0, LOG) up[pw].resize(n);
  tin.assign(n, -1);
  tout.assign(n, -1);
  xr.assign(n, 0);

  T = 0;
  fore(v, 0, n) {
    if (tin[v] != -1)
      continue;
    dfs(v, v, 0);
  }
}

int isPar(int p, int v) { return tin[p] <= tin[v] && tout[v] <= tout[p]; }

int lca(int u, int v) {
  if (isPar(u, v))
    return u;
  if (isPar(v, u))
    return v;

  for (int pw = LOG - 1; pw >= 0; pw--) {
    if (!isPar(up[pw][v], u))
      v = up[pw][v];
  }
  return up[0][v];
}

vector<int> par, rk;

void init(int n) {
  par.assign(n, 0);
  iota(par.begin(), par.end(), 0);
  rk.assign(n, 1);
}

int top(int v) {
  if (par[v] != v)
    return par[v] = top(par[v]);
  return v;
}

bool unite(int u, int v) {
  u = top(u);
  v = top(v);
  if (u == v)
    return false;
  if (rk[u] < rk[v])
    swap(u, v);
  par[v] = u;
  rk[u] += rk[v];
  return true;
}

vector<int> F;
void inc(int pos, int val) {
  for (; pos < sz(F); pos |= pos + 1)
    F[pos] += val;
}
int sum(int pos) {
  int ans = 0;
  for (; pos >= 0; pos = (pos & (pos + 1)) - 1)
    ans += F[pos];
  return ans;
}

void addOnPath(int v, int l) {
  while (v != l) {
    inc(tin[v], 1);
    inc(tout[v], -1);
    v = up[0][v];
  }
}

inline void solve() {
  init(n);
  g.resize(n);

  vector<int> ans(q, -1);
  fore(i, 0, q) {
    int u = es[i][0];
    int v = es[i][1];
    int x = es[i][2];

    if (unite(u, v)) {
      ans[i] = 1;
      g[u].emplace_back(v, x);
      g[v].emplace_back(u, x);
    }
  }

  buildLCA();
  F.assign(2 * n + 5, 0);

  fore(i, 0, q) {
    if (ans[i] != -1)
      continue;

    ans[i] = 0;
    int u = es[i][0];
    int v = es[i][1];
    int x = es[i][2];

    int xorPath = xr[u] ^ xr[v];
    if ((xorPath ^ x) != 1)
      continue;

    int l = lca(u, v);
    int usedOnPath = sum(tin[u]) + sum(tin[v]) - 2 * sum(tin[l]);
    if (usedOnPath > 0)
      continue;

    ans[i] = 1;
    addOnPath(u, l);
    addOnPath(v, l);
  }

  for (int res : ans)
    cout << (res ? "YES" : "NO") << '\n';
}

int main() {
  cout << fixed << setprecision(15);
  if (read()) {
    solve();
  }
  return 0;
}
:hourglass::open_file_folder::bulb:
191873.1尺取 + 线段树