题解 AtCoder Regular Contest 184 / ARC184A~E

A - Appraiser

如果可以询问 n1 次就好了。那样的话我们只需要拿硬币 1 和其他所有硬币逐个询问一下,就能通过与 1 类型相同的硬币的数量判断哪些硬币是可疑的。也就是说,有 n 个硬币时,可以通过 n1 次操作将这些硬币划分为两个集合,其中一个集合是可疑的,另外的集合都能排除嫌疑。

为此,我们将 n 枚硬币划分为 50 个集合,每个集合 20 枚,每个集合内部进行上述的算法,除非划分的两个集合大小都是 10,否则数量少的集合一定是可疑的。而前者的情况只会出现至多一次,且出现时说明其他集合的硬币都不可疑,只需要拿集合中任意一枚硬币和已经排出嫌疑的硬币询问即可。一共 951 次询问。

为了卡这剩下的一次询问,我们拿最后一个大小为 20 的集合出来,单独讨论。如果在处理最后一个集合前,已经发现了 10 枚可疑的硬币,那么最后一个集合不用处理;否则如果发现了至少 1 枚,说明之前没有遇到过 10,10 的情况,最后一个集合运行上面的算法;否则可能出现 10,10,为了避免这种情况,考虑拿出最后一个集合的两枚硬币,做如下分讨:

  • 如果其他 18 枚硬币划分的最大的集合超过 10,可以发现这是不可能的,因为这时可疑的硬币数量不够。
  • 那么只有两种情况:划分了 9,98,10,剩余两次询问。
  • 9,9:随意询问一枚硬币与不可疑的硬币,这样可以直接找到 9 枚可疑硬币,剩下两枚直接随意拿一枚和不可疑的硬币询问,找到最后一枚。
  • 8,10:随意询问 10 集合中的一枚硬币与不可疑的硬币,如果发现是可疑的,那么这 10 枚硬币都是可疑的硬币;否则另外 8 枚硬币和没询问的 2 枚硬币都是可疑的硬币。

询问次数刚好 950,可以通过。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
int n, m, q;
vector<int> ans;
bool query(int x, int y) {
  cout << "? " << x << " " << y << endl;
  int ret;
  cin >> ret;
  return ret;
}
void work(int l, int r) {
  vector<int> st[2];
  st[0].push_back(l);
  for (int i = l + 1; i <= r; i++) st[query(l, i)].push_back(i);
  if (st[0].size() == st[1].size()) {
    int tmp = l == 1 ? r + 1 : 1;
    ans = st[!query(tmp, st[0].front())];
  } else {
    if (st[0].size() > st[1].size()) swap(st[0], st[1]);
    for (int x : st[0]) ans.push_back(x);
  }
}
int main() {
#ifndef LOCAL
#endif
  cin >> n >> m >> q;
  for (int i = 0; i < 50 - 1; i++) work(i * 20 + 1, (i + 1) * 20);
  if (!ans.empty() && ans.size() < 10) work(49 * 20 + 1, 50 * 20);
  else if (ans.empty()) {
    int l = 49 * 20 + 1, r = 50 * 20;
    vector<int> st[2];
    st[0].push_back(l);
    for (int i = l + 1; i <= r - 2; i++) st[query(l, i)].push_back(i);
    if (st[0].size() < st[1].size()) swap(st[0], st[1]);
    if (st[0].size() == 10) {
      if (query(st[0][0], 1)) ans = st[0];
      else ans = st[1], ans.push_back(r - 1), ans.push_back(r);
    } else {
      if (query(st[0][0], 1)) ans = st[0];
      else ans = st[1];
      if (query(r, 1)) ans.push_back(r);
      else ans.push_back(r - 1);
    }
  }
  cout << "! ";
  for (int x : ans) cout << x << " ";
  cout << endl;
  return 0;
}

B - 123 Set

考虑将数字写为 2p23p3C 的形式,其中 gcd(C,6)=1。这样以后,将 C 相同的数字放在一起,可以发现将它们按照 (p2,p3) 的坐标写在纸上,将组成一个阶梯形网格图,操作就是将 (p2,p3),(p2+1,p3),(p2,p3+1) 加进去。由于较短的一边的长度为 log3109+1<20,可以对这条边做状压 dp。有一个枚举集合有交并的部分,我们使用 FWT 将所有位置同步打一份到它的子集上,将有交并改成无交并。C=1 时计算量约为 20×220×log2109。大概是可以接受的。

但是 C 会有 O(n) 个,即使你发现 C=6k+16k+5 将常数除以 3 也难以通过。我们发现因为我们的限制是 2p23p3Cn 也就是 2p23p3n/C,可以直接对 n/C 整除分块,将 n/C 相同的 C 直接一起计算,可以通过本题。复杂度不知道。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
#define popcount __builtin_popcount
using LL = long long;
void work(vector<int>& a) {
  for (int j = 0; 1 << j < a.size(); j++) {
    for (size_t i = a.size(); i--; ) {
      if (i >> j & 1) a[i ^ (1 << j)] = min(a[i ^ (1 << j)], a[i]);
    }
  }
}
int solve(int n) {
  vector<int> a;
  for (LL now = n; now; now /= 3) a.push_back(__lg(now));
  vector<int> b(a[0] + 1);
  for (size_t i = 0; i < a.size(); i++) b[a[i]] = i + 1;
  for (int i = a[0]; i >= 1; i--) b[i - 1] = max(b[i - 1], b[i]);
  vector<int> f(1 << b[0], 1e9);
  f[0] = 0;
  for (int i = 0; i <= a[0]; i++) {
    debug("b[%d] = %d\n", i, b[i]);
    int U = (1 << b[i]) - 1;
    for (size_t i = 0; i < f.size(); i++) f[i & U] = min(f[i & U], f[i]);
    f.resize(1 << b[i]);
    vector<int> nxt(1 << b[i], 1e9);
    work(f);
    for (int S = 0; S < 1 << b[i]; S++) {
      int T = (S | S << 1) & U;
      nxt[S] = min(nxt[S], popcount(S) + f[U ^ T]);
    }
    f = move(nxt);
  }
  debug("solve(%d) = %d\n", n, *min_element(f.begin(), f.end()));
  return *min_element(f.begin(), f.end());
}
int n, ans;
int calc(int r) {
  return (r >= 1 ? (r - 1) / 6 + 1 : 0) + (r >= 5 ? (r - 5) / 6 + 1 : 0);
}
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);  
#endif
  cin >> n;
//ans += (n - 1) / 6 + 1;
//if (n >= 5) ans += (n - 5) / 6 + 1;
//for (int x = 0; x <= n / 3 - 1; x += 6) ans += solve(n / (x + 1)) - 1;
//for (int x = 0; x <= n / 3 - 5; x += 6) ans += solve(n / (x + 5)) - 1;
  for (int l = 1, r; l <= n; l = r + 1) {
    r = n / (n / l);
    if (calc(l - 1) != calc(r)) ans += solve(n / l) * (calc(r) - calc(l - 1));
  }
  cout << ans << endl;
  return 0;
}

C - Mountain and Valley Folds

你去找一张纸折一下,折 k 次,然后将折痕剪掉,发现 2k 张小纸是反面朝上-正面朝上-……-反面朝上-正面朝上的顺序堆叠的。再把折痕搞回来,对 2k1 条折痕,从 1 开始编号,那么所有奇数项是 01010101,去掉所有奇数项后又有这个规律,直到删空。归纳一下,x 是 Mountain Fold 当且仅当 x/lowbit(x)2 是奇数。

那么做法就很显然了,搞个类似匹配的东西,对于 i+ak,枚举 lowbit=2j,当 i+ak 的长度为 j 的前缀全零时,将 i 的这段前缀插入 Trie 树,然后枚举 lowbit 上一位取什么值,找这个儿子打个标记。答案就是 Trie 上标记最多的一条链。O(nlogai)

点击查看代码
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
constexpr int N = 3e5;
int n, ch[N][2], tot, cnt[N], ans;
int conbit(LL x, int b) { return b <= 60 ? x >> b & 1 : 0; }
void dfs(int u, int now) {
  ans = max(ans, now += cnt[u]);
  for (int r : {0, 1}) if (ch[u][r]) dfs(ch[u][r], now);
}
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);  
#endif
  cin >> n;
  for (int i = 1; i <= n; i++) {
    LL x;
    cin >> x;
    int p = 0, u = 0;
    for (int j = 0; j < 100; j++) {
      for (int r : {0, 1}) if (!ch[u][r]) ch[u][r] = ++tot;
      int b = conbit(x, j), r = p + b == 1;
      for (int rp : {0, 1}) {
        int v = ch[u][!r];
        if ((conbit(x, j + 1) + (!r + b + p) / 2 + rp) & 1) {
          if (!ch[v][rp]) ch[v][rp] = ++tot;
          cnt[ch[v][rp]] += 1;
        }
      }
      p = (r + b + p) >> 1;
      u = ch[u][r];
    }
  }
  dfs(0, 0);
  cout << ans << endl;
  return 0;
}

D - Erase Balls 2D

定义 a[i] 为横坐标是 i 的小球的纵坐标。发现选择的任意两个 k 必然不能满足那个偏序关系(k1<k2,a[k1]<a[k2]),也就是说只能 a[k1]>a[k2]。不妨直接枚举选择的 k 的序列,dpi 表示当前最后一个 ki,然后可以转移。但是可能会算重,我们只需要保证 dpidpj 的转移中间不存在 k(i,j) 使得 dpidpkdpjdpidpj 两种转移能保留的球一样就能转移过去。记一个 cnti,j 表示 k=ij[ai>ak>aj],直接加一下判断有没有多的点。O(n3)

点击查看代码
#include "atcoder/modint"
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
using mint = atcoder::modint998244353;
int n, a[310];
mint f[310], c[310][310];
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);  
#endif
  cin >> n;
  for (int i = 1, x, y; i <= n; i++) cin >> x >> y, a[x] = n - y + 1;
  f[0] = 1;
  a[n + 1] = n + 1;
  for (int i = 1; i <= n; i++) {
    for (int l = 0; l < i; l++) if (a[l] < a[i]) {
      for (int r = i + 1; r <= n + 1; r++) if (a[i] < a[r]) c[l][r] += 1;
    }
  }
  for (int i = 0; i <= n; i++) {
    for (int j = i + 1; j <= n + 1; j++) if (a[i] < a[j]) {
      bool flag = false;
      for (int k = i + 1; k < j && !flag; k++) if (a[i] < a[k] && a[k] < a[j] && c[i][k] + c[k][j] + 1 == c[i][j]) flag = true;
      if (!flag) f[j] += f[i];
    }
  }
  cout << f[n + 1].val() << endl;
  return 0;
}

E - Accumulating Many Times

首先不要读错题目了。然后我们稍微想一下怎么求单个 f。为了求这个,我们至少需要会快速做 x 次操作。我们有一个经典的东西,就是考虑 b 为一个长为 m 的 0/1 数组,然后 x 次操作后,从 bi 走到 bj(ji+x1x1) 种走法(每次可以前进任意非负整数步,插板法),所以 bibj 的贡献是 bi(ji+x1x1)mod2。可以写一个 O(m2) 的东西,如果你观察到那坨东西 =[zji](1z)x 然后也发现不了什么 0/1 序列卷积有什么性质,你就不得不写一个卷积,将 i=0m1bizi(1z)x 做卷积,O(mlogm) 的时间就可以快速对 bx 次操作。

但是单个 f 没有二分性,还是比较自闭,但是我们看这个 (ji+x1x1)mod2 因为有个 Lucas 定理可以写成 [x1ji+x1],先小小换个元 stp=x1 那就是 [stpji+stp],观察到如果 stpji 还大,就是 stp 的二进制上有一些位比 ji 的最高位还高,去掉它必然不影响结果,也就是说 stp 会有个关于 ji 的周期。于是就有一个方法:先确定到达前 20 个正确需要多少步,然后到达前 21 正确需要多少步(这里只有两种决策 +0+20),然后到达前 22 正确需要多少步(这里只有两种决策 +0+21)……以此类推,就可以求出单个 f(当然有很大可能无法相等,直接退出)复杂度还是 O(mlogm)

接下来的一步是尝试将可以互相转化的 b 缩成等价类,我们先去掉前导零,然后将 b 变化为这样的形式:b0,b1,b2,b4,b8, 都为 1,显然如果可以互相转化则都可以转化到这种状态,而且和刚才的方法差不多。缩完等价类后,就知道了到达这个代表元的步数,自然互相转化的步数就是到达代表元的步数的差,当然要和 2 的某个次幂取模,用树状数组维护一下这个取模要加多少个 2kO(nmlogm)

点击查看代码

atcoder:: 含量达到了惊人的 4

#include "atcoder/all"
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
using mint = atcoder::modint998244353;
int n, m;
vector<int> perf(const vector<int>& vec, int stp) {
  int n = vec.size();
  vector<int> tmp(n);
  for (int j = 0; j < n; j++) tmp[j] = (stp & (j + stp)) == stp;
  auto&& ret = atcoder::convolution<998244353>(vec, tmp);
  for (int j = 0; j < n; j++) tmp[j] = ret[j] & 1;
  return tmp;
}
auto work() {
  vector<int> vec(m);
  for (int i = 0; i < m; i++) cin >> vec[i];
  auto bg = vec.begin(), ed = vec.end();
  while (bg != vec.end() && *bg == 0) ++bg;
  int stp = 0, sz = ed - bg;
  for (int j = 1; j < sz; j <<= 1) {
    vector<int> slic(bg, bg + min(j << 1, sz));
    if (!perf(slic, stp)[j]) stp += j, assert(perf(slic, stp)[j]);
  }
  return make_pair(perf(vec, stp), stp);
}
map<vector<int>, vector<int>> mp;
int main() {
#ifndef LOCAL
  cin.tie(nullptr)->sync_with_stdio(false);  
#endif
  cin >> n >> m;
  for (int i = 1; i <= n; i++) {
    auto&& e = work();
    mp[e.first].push_back(e.second);
  }
  mint ans = 0;
  for (auto&& e : mp) {
    int pos0 = 0;
    while (pos0 < (int)e.first.size() && e.first[pos0] == 0) ++pos0;
    if (pos0 == (int)e.first.size()) continue;
    int p = atcoder::internal::bit_ceil(e.first.size() - pos0);
    atcoder::fenwick_tree<mint> fw0(p);
    mint prev = 0, pre0 = 0;
    for (auto&& x : e.second) {
      ans += prev - pre0 * x + fw0.sum(0, x) * p;
      prev += x, pre0 += 1, fw0.add(x, 1);
    }
  }
  cout << ans.val() << endl;
  return 0;
}

update:避免使用 NTT 的方法,参见 https://www.luogu.com/article/1z7le77j

posted @   caijianhong  阅读(166)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
历史上的今天:
2023-09-23 题解 Gym 104077I【[ICPC2022 Xi'an R] Square Grid】
2023-09-23 题解 CF1257G【Divisor Set】
2023-09-23 题解 ARC165F【Make Adjacent】
2023-09-23 题解 SS230923C【国境线(boundary)】
2023-09-23 题解 SS230922B【快哭了 (kk)】
2023-09-23 题解 SS230922A【糗大了 (qd)】
2023-09-23 题解 SS230922D【发怒 (fn)】
点击右上角即可分享
微信分享提示