Loading

08.29

QOJ141

A 没必要传度数 \(<8\) 的点。

因为双染色是容易的,A 把两种颜色压缩成一种颜色,B 再把每种颜色双染色,就是合法的八染色了。

每个点给度数和贡献至少 \(8\),占 \(2\) bit,考虑到度数和的上限为 \(2m\),至多需要 \(m/2\) bit。

std::vector <int> Alice(int n, int m, std::vector <int> u, std::vector <int> v, std::vector <int> c) {
  std::vector<int> deg(n);
  for (int i = 0; i < m; i++) {
    ++deg[u[i]], ++deg[v[i]];
  }
  std::vector<int> x;
  for (int i = 0; i < n; i++) if (deg[i] >= 8) {
    x.push_back(c[i] >> 2), x.push_back((c[i] >> 1) & 1);
  }
  return x;
}

std::vector <int> Bob(int n, int m, std::vector <int> u, std::vector <int> v, std::vector <int> x) {
  std::vector<int> deg(n);
  std::vector<std::vector<int>> e(n);
  for (int i = 0; i < m; i++) {
    ++deg[u[i]], ++deg[v[i]];
    e[u[i]].push_back(v[i]), e[v[i]].push_back(u[i]);
  }
  std::vector<int> c(n, -1);
  int cnt = 0;
  for (int i = 0; i < n; i++) if (deg[i] >= 8) {
    c[i] = x[cnt++], c[i] = 2 * c[i] + x[cnt++];
  }
  std::vector<int> col(n, -1);
  std::function<void(int)> dfs = [&](int u) {
    for (auto v : e[u]) if (col[v] == -1 && c[u] == c[v] && c[u] != -1)
      col[v] = col[u] ^ 1, dfs(v);
  };
  for (int i = 0; i < n; i++) if (col[i] == -1)
    col[i] = 0, dfs(i);
  for (int i = 0; i < n; i++) if (c[i] != -1) c[i] = 2 * c[i] + col[i]; 
  for (int i = 0; i < n; i++) if (c[i] == -1) {
    std::vector<int> col(8);
    for (auto v : e[i]) if (c[v] != -1) col[c[v]] = 1;
    for (int j = 0; j < 8; j++) if (!col[j])
      c[i] = j;
  }
  return c;
}

P10218

只要还有数字不在 \(S\) 集合中,答案就不会超过 \(2^k-1\)。因此当且仅当 \(\sum b_i \leq m\) 时答案大于 \(2^k-1\) 且为 \(\min\{b_i\}+2^k-1\),否则答案恰有 \(k\) 位。

尝试逐位确定之。

那么把所有 \(a_i\) 插入 Trie,在 Trie 上考虑所有前 \(i\) 位与答案相同的数(不同的话,最高不同位必须大于答案对应位)。做 dfs,传入剩余的 \(m\)。在一个点处枚举 \(x\) 的当前位为 \(0\) 还是 \(1\),事实上是对称的。假如是 \(0\),则左子树中所有点必须被划入 \(S\) 集合,子树内点的 \(b_i\) 和不超过剩余的 \(m\),且 \(a_i\) 的最小值经过 \(+x\) 后超过当前答案。递归另一侧确定后面位即可。

CF1887E

矩形不好处理,但点并不多。把 \((x, y)\) 的颜色为 \(c\) 处理成 \(x \to y+n\) 有一条 \(c\) 的边,抓一个初始边构成的环,每次从正中间劈开,一定至少一个子环没有重复颜色。这样做下去就行了。\(10 > \log_2 n\),所以一定存在方案。

有人输入的颜色处理成 0-index 了。

#include <bits/stdc++.h>

int f[2000][2000];
char s[6];

void solve() {
  int n; scanf("%d", &n);
  std::vector<std::vector<int>> e(2 * n);
  for (int i = 1, u, v; i <= 2 * n; i++) {
    scanf("%d %d", &u, &v), --u, --v, v += n;
    e[u].push_back(v), e[v].push_back(u);
    f[u][v] = f[v][u] = i;
  }
  std::vector<int> stk, path, vis(2 * n);
  std::function<void(int, int)> dfs = [&](int u, int f) {
    if (vis[u]) {
      if (!path.size()) {
        while (stk.size() && stk.back() != u)
          path.push_back(stk.back()), stk.pop_back();
        path.push_back(u), stk.pop_back();
      }
      return;
    }
    assert(u < (int)vis.size());
    vis[u] = 1;
    stk.push_back(u);
    for (auto v : e[u]) if (v != f)
      dfs(v, u);
    if (stk.size() && stk.back() == u) stk.pop_back();
  };
  for (int i = 0; i < n; i++) if (!vis[i])
    dfs(i, -1);
  while ((int)path.size() > 4) {
    int h = path.size() % 4 == 0 ? path.size() / 2 - 1 : path.size() / 2;
    int u = path[0], v = path[h];
    if (u > v) printf("? %d %d\n", v + 1, u - n + 1);
    else printf("? %d %d\n", u + 1, v - n + 1);
    fflush(stdout);
    int t; scanf("%d", &t);
    f[u][v] = f[v][u] = t;
    bool c = 1;
    for (int i = 0; i < h; i++)
      c &= (f[path[i]][path[i + 1]] != t);
    if (c) 
      path = std::vector<int>(path.begin(), path.begin() + h + 1);
    else
      path = std::vector<int>(path.begin() + h, path.end()), path.push_back(u);
  }
  int a = path[0], b = path[1], c = path[2], d = path[3];
  if (a < n) printf("! %d %d %d %d\n", a + 1, c + 1, b - n + 1, d - n + 1);
  else printf("! %d %d %d %d\n", b + 1, d + 1, a - n + 1, c - n + 1);
  fflush(stdout);
  scanf("%s", s);
}

int main() {
  int T; scanf("%d", &T); while (T--) {
    solve();
  }
}

agc044D

编辑距离可以做什么呢?对于一个全 A 的串,编辑距离可以求出来密码中有几个 A(只使用替换删除);对于 \(T\)\(S\),二者为子序列当且仅当编辑距离 \(=|S|-|T|\)(只插入)。

于是求 \(f(S)\) 表示只保留 \(S\) 中的字符密码是什么样子的,合并 \(f(A)\)\(f(B)\) 需要 \(|A|+|B|-1\) 次操作,哈夫曼树优化合并(不优化好像也行)就行了。

至于这个合并,跑一个归并排序,枚举下一位来自哪个串就行了。也可以把一个串的每一位往另一个串里面插。

#include <bits/stdc++.h>

struct cmp {
  bool operator () (const std::string &x, const std::string &y) {
    return x.length() > y.length();
  }
};

int main() {
  std::priority_queue<std::string, std::vector<std::string>, cmp> q;
  auto qry = [&](std::string s) {
    std::cout << "? " << s << std::endl;
    int t; std::cin >> t; return t;
  };
  int l = 0;
  std::vector<char> ch;
  for (int i = 0; i < 26; i++)
    ch.push_back(i  + 'a'), ch.push_back(i + 'A');
  for (int i = 0; i < 10; i++)
    ch.push_back(i + '0');
  for (auto c : ch) {
    int x = 128 - qry(std::string(128, c));
    if (x) q.push(std::string(x, c));
    l += x;
  }
  while (q.size() > 1) {
    std::string a = q.top(); q.pop();
    std::string b = q.top(); q.pop();
    int n = a.length(), m = b.length();
    int i = 0, j = 0;
    std::string c;
    while (i < n && j < m) {
      std::string s = c + a[i] + std::string(b.begin() + j, b.end());
      if (qry(s) == l - s.length())
        c = c + a[i++];
      else
        c = c + b[j++]; 
    }
    while (i < n) c = c + a[i++];
    while (j < m) c = c + b[j++];
    q.push(c);
  }
  std::cout << "! " << q.top() << std::endl;
}

CF1372F

\(solve(l, r)\) 求出 \([l, r]\) 区间内的具体值。

假设已知 \(a_p=x\)\(x\) 出现 \(y\) 次,询问 \([p-y+1, p]\) 以及 \([p, p+y-1]\) 即可确定出现范围,然后即可分治两边。

注意到两个信息是可以分开求的,只要每次的 \(x\) 彼此不同,复杂度刚好有保障。于是若知道 \([l, r]\) 的众数为 \(x\)\(x\) 出现 \(y\) 次,且还不知道一个值为 \(x\) 的位置,从区间中最大的没确定值的位置开始往右找,每次跳 \(y\) 个位置,一定能找到一个满足 \(a_p=x\) 的位置。

好像最小众数的最小没有用诶。

感觉蓝不了一点。

不是很懂正确性,经过测试每次查单点时查到的确实两两不同。还是 CF 题解更靠谱。

CF1368E

如果图是树,删所有深度模三相同的点即可。

在拓扑序上贪心做这个过程,若入边全为 \(A\) 则划为 \(B\),若入边有 \(B\) 则划为 \(C\),否则全为 \(C\),则划入 \(A\)

可以发现 \(2 |A| \geq |B|\)\(2|B| \geq |C|\),删去 \(C\) 是合法的。

#include <bits/stdc++.h>

void solve() {
  int n, m; scanf("%d %d", &n, &m);
  std::vector<std::set<int>> e(n);
  std::vector<std::vector<int>> nxt(n);
  std::vector<int> deg(n);
  for (int i = 0, x, y; i < m; i++) {
    scanf("%d %d", &x, &y), --x, --y;
    e[y].insert(x), ++deg[y];
    nxt[x].push_back(y);
  }
  std::queue<int> q;
  for (int i = 0; i < n; i++) if (!deg[i])
    q.push(i);
  std::vector<int> col(n), ans;
  while (q.size()) {
    int u = q.front(); q.pop();
    for (auto v : e[u]) {
      if (col[v] == 1) col[u] = 2;
      if (col[u] != 2 && col[v] == 0) col[u] = 1;
    }
    for (auto v : nxt[u]) if (!--deg[v]) q.push(v);
    if (col[u] == 2) ans.push_back(u);
  }
  printf("%d\n", ans.size());
  for (auto x : ans) printf("%d ", x + 1);
}

int main() {
  int T; scanf("%d", &T); while (T--) {
    solve();
  }
}

CF1148F

逐位确定 \(s\),每当 \(s \& msk_i\) 出现一个 \(1\) 就翻转一次 \(val_i\)

从低位到高位贪心是没有后效性的。不妨认为初始和为正数,每次考虑所有 \(msk_i\) 最高位为 \(x\) 的数值,如果其 \(val_i\) 和为正则翻转并更新所有 \(val_i\) 并记 \(s\) 的第 \(x\) 位为 \(1\),否则这一位为 \(0\)

有一点二进制拆分的感觉,但事实上不是。更像一个分组,每个数恰好被计入答案一次,且计入答案那一次是最后一次,因此保证了每一组都是正贡献。

#include <bits/stdc++.h>

using LL = long long;

int main() {
  int n; scanf("%d", &n);
  std::vector<int> val(n);
  std::vector<LL> msk(n);
  std::vector<std::vector<int>> b(64);
  LL sum(0);
  for (int i = 0; i < n; i++) {
    scanf("%d %lld", &val[i], &msk[i]), sum += val[i];
    for (int j = 63; j >= 0; j--) if (msk[i] & (1ll << j)) {
      b[j].push_back(i); break;
    }
  }
  if (sum < 0) {
    sum = -sum;
    for (int i = 0; i < n; i++)
      val[i] = -val[i];
  }
  LL s(0);
  for (int i = 0; i < 64; i++) {
    LL tot(0);
    for (auto j : b[i]) tot += val[j];
    if (tot > 0) {
      s |= 1ll << i;
      for (int j = 0; j < n; j++) if (msk[j] & (1ll << i))
        val[j] = -val[j];
    }
  }
  printf("%lld\n", s);
}

CF1270G

\(a_i-i \in [-1, -n]\),如果把 \(a_i-i\) 当成权值,那么要找一个集合,下标和与权值和为 \(0\)

连边 \(i \to i-a_i\),找一个环即可。

#include <bits/stdc++.h>

void solve() {
  int n; scanf("%d", &n);
  std::vector<int> a(n + 1);
  for (int i = 1; i <= n; i++)
    scanf("%d", &a[i]);
  std::vector<int> stk, path, vis(n + 1);
  std::function<void(int)> dfs = [&](int u) {
    if (vis[u]) {
      if (!path.size()) {
        while (stk.size() && stk.back() != u)
          path.push_back(stk.back()), stk.pop_back();
        path.push_back(u), stk.pop_back();
      }
      return;
    }
    vis[u] = 1, stk.push_back(u);
    dfs(u - a[u]);
    if (stk.size() && stk.back() == u) stk.pop_back();
  };
  dfs(1);
  printf("%d\n", path.size());
  for (auto i : path) printf("%d ", i);
  printf("\n");
}

int main() {
  int T; scanf("%d", &T); while (T--) {
    solve();
  }
}
posted @ 2024-08-29 16:44  purplevine  阅读(15)  评论(0编辑  收藏  举报