【比赛记录】CodeChef January Challenge 2021

打完 div3 的 900 pts 就跑路所以只有前面 9 题题解

【DIVTHREE】 Chef and Division 3

输出 (minleft(d, leftlfloorfrac{sum_{i=1}^n a_i}{k} ight floor ight)) 即可。

【DECODEIT】Encoded String

对于输入串的每个四位进行解密。类似于二分:一开始答案区间为 ([ exttt a, exttt p])。然后对于当前位,如果为 (0) 那么取左半部分,反之则为右半部分。

【FAIRELCT】Fair Elections

一个显然的贪心思路:每次选择最小的 (A) 与最大的 (B) 交换。那么维护两个 std::multiset 表示选票集合可以很方便完成这个操作。当出现最小的 (A) 大于最大的 (B) 是输出 -1

【BILLRD】Point Of Impact

考虑到桌面是个 (N imes N) 的正方形,于是这个球要么直接撞到桌角然后停止,要么永远撞不到桌角并有四个固定的撞击点。于是随便分讨就做完了,然而细节尤其要注意。

【WIPL】Watching CPL

题意:你有 (N) 个箱子,第 (i) 个高度 (H_i(le 10^5))。你需要用这些盒子搭出两座塔,高度均 (ge K)。求最少需要几个盒子。(N, Kle 4 imes 10^3)

首先毋庸置疑的是,选相同个数的盒子的情况下,必然去选高度大的。那么不妨将盒子按大小降序排序,然后二分答案前 (x) 个。

然后就是一个类似与背包的东西:求出用这些盒子可以到达的高度集合。对于每一个可到达的高度 (i),两座塔的高度就分别是 (i)((sum_{j=1}^x a_j)-i)。显然只要判断高度集合中 (K) 的前驱后继即可。

最后就是如何求出高度集合 (T),方便操作先将 (T) 压成一个 std::bitset。设当前考虑到第 (i) 个物品,(Tgets (Tll H_i) | T) 即可。复杂度 (Oleft(frac{NH}{w}log N ight))。代码出乎意料的好写:

const int N = 4e3 + 5;
const int H = 2e5 + 5;
int n, K, h[N];
std::bitset<H> f;

bool judge(int k) {
  f.reset(), f.set(0); long long s = 0;
  for (int i = 1; i <= k; i++)
    f |= (f << h[i]), f.set(h[i]), s += h[i];
  int L = f._Find_next(K - 1);
  if (L != H && s - L >= K) return 1;
  return 0;
}

void solve() {
  std::cin >> n >> K;
  for (int i = 1; i <= n; i++)
    std::cin >> h[i];
  std::sort(h + 1, h + 1 + n, std::greater<int>());
  int lb = 1, ub = n, ans = n + 1;
  while (lb <= ub) {
    int mid = (lb + ub) / 2;
    if (judge(mid)) ans = mid, ub = mid - 1;
    else lb = mid + 1;
  }
  if (ans == n + 1) std::cout << -1 << std::endl;
  else std::cout << ans << std::endl;
}

【ANTSCHEF】Chef and Ants

(N(le 2 imes 10^5)) 条不重合但交于一点 (O) 的直线,第 (i) 条直线上有 (M_i(M_ile 5 imes 10^5, sum_i M_i le 10^6)) 只蚂蚁,位置分别为 (X_{i, 1}, X_{i, 2}, cdots, X_{i, M_{i}}(|X_{i,j}in [1, 10^9]|))。一开始所有蚂蚁面向 (O) 点同速度移动。当有两只蚂蚁发生碰撞时,所有碰撞的蚂蚁都会同时反向。求最终碰撞总次数(多只蚂蚁算一次)。

根据套路,蚂蚁相撞看做擦肩而过最好处理。对于多只蚂蚁在 (O) 处相撞的情况,不能简单地按原路继续走下去,和另外那些不会在 (O) 相撞的存在本质区别。那就先按是否会在 (O) 处发生相撞分为两类:不相撞的为孤立点,反之为非孤立点。

首先对于一个非孤立点,与之互相产生贡献的点有,这个点后面的点,以及在 (O) 处相撞的点。

然后考虑孤立点,与之产生贡献的除了同线上的其他孤立点,还有某些非孤立点。这些非孤立点会在到达 (O) 之前与这个孤立点产生贡献。

很显然这种题根本讲不清楚于是这里放一个我的阴间实现,复杂度一只 (log)STL 党狂喜

const int N = 2e5 + 5;
const int M = 1e6 + 5;

int n, m[N];
std::vector<int> x[N], t[N], v[N];
std::map<int, std::set<std::pair<int, int> > > l;
long long ans = 0;

inline int countPre(std::vector<int>& a, int p) {
  return std::lower_bound(a.begin(), a.end(), p) - a.begin();
}
inline int countNxt(std::vector<int>& a, int p) {
  return a.end() - std::upper_bound(a.begin(), a.end(), p);
}
inline void clear() {
  for (int i = 1; i <= n; i++) x[i].clear();
  for (int i = 1; i <= n; i++) t[i].clear();
  for (int i = 1; i <= n; i++) v[i].clear();
  l.clear(), ans = 0;
}

void solve() {
  std::cin >> n;
  for (int i = 1; i <= n; i++) {
    std::cin >> m[i], x[i].resize(m[i]);
    for (auto& it : x[i]) std::cin >> it, l[abs(it)].emplace(i, it);
    std::sort(x[i].begin(), x[i].end());
  }

  for (auto& s : l) {
    if (s.second.size() == 1u) {
      t[s.second.begin()->first].push_back(s.second.begin()->second);
      continue;
    }
    for (auto i = s.second.begin(); i != s.second.end(); i++) {
      if (i->second < 0) ans += countPre(x[i->first], i->second);
      else ans += countNxt(x[i->first], i->second);
      v[i->first].push_back(i->second);
    }
    ++ans;
  }

  for (int i = 1; i <= n; i++) if (!t[i].empty()) {
    std::sort(t[i].begin(), t[i].end());
    std::sort(v[i].begin(), v[i].end());
    for (auto it = t[i].begin(); it != t[i].end(); it++) {
      if (*it < 0) ans += countNxt(t[i], 0), ans += countNxt(v[i], -*it);
      else ans += countPre(v[i], -*it);
    }
  }

  std::cout << ans << std::endl;
  clear();
}

【TILESQRS】Guess the Tiling

你在一个大小为 (N imes N) 的房间中,地上铺有 (N)(N) 列正方形地砖,地砖有两种花纹,为正反两种对角线。其中一组 (2 imes 2) 的地砖在调整后可以构成一个 (sqrt 2 imes sqrt 2) 的正方形。其中“调整”指将某块地砖旋转 (90^circ)

这是一道交互题。一开始你并不知道初始局面,只知道 (N(le 200))(K)(初始局面中构成的 (sqrt 2 imes sqrt 2) 的正方形(下称“次小正方形”)的总个数),以及你可以调整的最大次数 (Q(ge 4cdot N^2))。接下来你可以进行若干次“调整”操作,选定一个地砖将其旋转,之后你又会马上得到新的 (K)

你需要在结束时输出结束时的局面。

首先一个 Naive 的想法是,通过 (2^4=16) 次调整(可以理解为格雷码)可以遍历一个 (2 imes 2) 的正方形的所有状态,尽管并不知道具体是什么。然后对于 (N = 2) 的情况我们可以这样搞:直接遍历这些状态,什么时候得到的 (K=1) 说明现在是一个次小正方形,就可以直接输出了。

然后这个简单的方法比较难以拓展到更大的 (N),因为这四块地砖还有可能和周围的构成其他的次小正方形。但是我们将原思路稍加改变:对于一个 (2 imes 2) 的区域,只遍历除第一个外的另外三个的 (2^3) 个状态,对于每个状态都试转一下第一块,根据前后的 (K) 的变化判断即可。

更具体的,对于 (N imes N) 的区域,四个一组处理。如果这个组在上或左边缘上,可以单纯根据 (K) 是否变化确定;而对内部的组而言,原先四周复杂的情况变得只需讨论左上角,又因为我们是顺序处理下来的,于是一旦凑成次小正方形,前后 (K) 的变化量一定为 (2)(左上已经凑成了)。

不过如果 (N) 为奇数是会出现剩下最后一行 & 列。这个不难搞,只要先降倒数第二行 & 列都转一遍,然后最后一行两块两块枚举状态即可。注意最后一块也需要特殊处理。

然后就做完了,不难发现每四个最多花费 (15) 次确定,不会超过交互次数。接下来放个代码:

const int N = 205;
const int order_x[7] = {1, 0, 1, 1, 1, 0, 1};
const int order_y[7] = {0, 1, 0, 1, 0, 1, 0};
int n, K, Q;
bool ans[N][N];

inline int test(int i, int j) {
  std::cout << "1 " << i << ' ' << j << std::endl;
  int ret; std::cin >> ret;
  if (ret == -1) std::cout << -1 << std::endl, exit(0);
  return ret;
}

#define abs(x) ((x) > 0 ? (x) : -(x))
inline bool invaild(int i, int j) {
  int cK = test(i, j);
  bool isIn = (i > 1) && (j > 1);
  if ((cK == K && !isIn) || (abs(cK - K) <= 1 && isIn))
    return K = cK, 1;
  ans[i + 1][j] = ans[j][i + 1] = 1;
  ans[i][j] = bool(cK < K), ans[i + 1][j + 1] = 0;
  return K = cK, 0;
}
#undef abs

void solve() {
  std::cin >> n >> Q >> K;
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= n; j++)
      ans[i][j] = 0;
  
  for (int i = 1; i < n; i += 2)
    for (int j = 1; j < n; j += 2)
      if (invaild(i, j)) for (int d = 0; d < 7; d++) {
        K = test(i + order_x[d], j + order_y[d]);
        if (!invaild(i, j)) break;
      }

  if (n & 1) {
    for (int i = 1; i < n; i++)
      K = test(i, n - 1), ans[i][n - 1] ^= 1;
    for (int j = 1; j < n - 1; j++)
      K = test(n - 1, j), ans[n - 1][j] ^= 1;

    for (int j = 1, cK; j < n; j += 2) {
      // (n, j), (n, j + 1)
      K = test(n, j + 1), cK = test(n, j);
      if (cK != K) {
        ans[n][j] = (cK > K), ans[n][j + 1] = 0, K = cK;
      } else {
        K = test(n, j + 1), cK = test(n, j);
        if (cK != K) ans[n][j] = (cK > K), ans[n][j + 1] = 0, K = cK;
      }
    }
    
    for (int i = 1, cK; i < n; i += 2) {
      // (i, n), (i + 1, n)
      K = test(i + 1, n), cK = test(i, n);
      if (cK != K) {
        ans[i][n] = (cK > K), ans[i + 1][n] = 0, K = cK;
      } else {
        K = test(i + 1, n), cK = test(i, n);
        if (cK != K) ans[i][n] = (cK > K), ans[i + 1][n] = 0, K = cK;
      }
    }

    K = test(n - 1, n - 1);
    K = test(n, n - 1), K = test(n - 1, n);
    ans[n - 1][n - 1] = 0;
    ans[n][n - 1] = ans[n - 1][n] = 1;
    ans[n][n] = bool(K > test(n, n));
  }

  std::cout << 2 << std::endl;
  for (int i = 1; i <= n; i++)
    for (int j = 1; j <= n; j++) {
      std::cout << ans[i][j];
      if (j == n) std::cout << std::endl;
      else std::cout << ' ';
    }
  
  int result; std::cin >> result;
  if (result == -1) exit(0);
}

【ORAND】And-Or Game

给定两个集合 (A,B)(值域 & 大小为 (2^{20}))。一开始你有一个数 (V),初值为 (0)。每次操作你可以:

  • 选出一个 (Xin A),执行 (V gets V lor X)(按位或);
  • 选出一个 (Xin B),执行 (V gets V land X)(按位与);

你可以进行任意轮(可以为 (0))操作,求 (V) 最终值可能有几个。

首先,一个数可以作为答案,必然可以表示为 (widehat a_1 land widehat b_1 lor widehat a_2 land widehat b_2 lor widehat a_3 land widehat b_3lor cdots) 的形式,其中 (widehat a) 表示 (A) 中选出若干个元素进行 (lor) 运算的结果,(widehat b) 同理。

考虑处理出 (widehat a)(widehat b)。不妨先试着能不能搞出选出两个进行运算的结果的集合,然后这个事情做 (|A|) 次就可以得到 (widehat a)。不难想到位运算卷积:(a^prime_i=sum_{jlor k=i} a_jcdot a_k)。注意这里 (a_i = [iin A])。使用 FMT 可以简单解决。拓展到 (widehat a),只要对 FMT 之后的结果跑一轮快速幂即可。(widehat b) 同理。这部分复杂度 (O(Nlog N))

然后就是将这些 (widehat a)(widehat b) 两者交错拼接。考虑一个性质:一个可能的最终结果至少可以由 (10)(widehat a)(widehat b) 构成。一次 (land)(lor) 操作可以使部分 (0/1) 位置反,如果这个操作没有效果显然可有可无,于是去除无效操作必然至少搞掉一个位,然后就卷 位数/2 次即可。

于是只要再做十次位运算卷积即可。总复杂度为小常数(不然怎么两 (log) 过百万)的 (O(Nlog^2 N))

inline int get() {
  return rand() % 100 + 1;
}
void solve() {
  read(n), read(m), t = U = 0;
  for (int i = 0, a; i < n; i++) A[read(a)] = get(), U = std::max(U, a);
  for (int i = 0, b; i < m; i++) B[read(b)] = get(), U = std::max(U, b);
  t = (int)log2(U) + 1, U = 1 << t;

  A[0] = get(), B[U - 1] = get();
  or_fmt(A, t, 0), and_fmt(B, t, 0);
  for (int i = 0; i < U; i++) A[i] = fastpow(A[i], n);
  for (int i = 0; i < U; i++) B[i] = fastpow(B[i], m);

  memcpy(C, A, U << 2), or_fmt(C, t, 1);
  for (int test = 1; test <= 10; test++) {
    and_fmt(C, t, 0);
    for (int i = 0; i < U; i++) C[i] = (LL)C[i] * B[i] % mod;
    and_fmt(C, t, 1), or_fmt(C, t, 0);
    for (int i = 0; i < U; i++) C[i] = (LL)C[i] * A[i] % mod;
    or_fmt(C, t, 1);
  }

  int ans = 0;
  for (int i = 0; i < U; i++) if (C[i]) ++ans;
  printf("%d
", ans);
  memset(A, 0, U << 2), memset(B, 0, U << 2), memset(C, 0, U << 2);
}

【BLKJK】Blackjack

你手头有 (N(le 10^3)) 张卡牌,第 (i) 张卡牌的权值为 (A_i(le 10^3))。你现在开始从第一张开始将卡牌发出。当某一时刻发出卡牌的权值和正好处于一个给定区间 ([X(le 10^3), Y(le 10^3)]) 内,那么你就赢了。如果这种时刻始终没有出现那么你就输了。现在有一个外挂,可以调换两张卡牌的顺序。你需要求出最少的调换次数是这副卡牌可以赢。

神必的 Bitset 标解:https://discuss.codechef.com/t/blkjk-editorial/83432

先 sto SegmentTree

本质上是要求调整后使存在一个前缀和落在区间 ([X,Y]) 内。那么我们枚举这个前缀和是由几个元素构成的,即枚举一个断点 (i),使前 (i) 个与后 (N-i) 个进行若干交换后可以进入 ([X,Y])

注意到一个事情,结果上面的转化后,已经限定了左右之间交换,因为内部换来换去没有意义。另外,也不应该有一个元素换过去又换回来。

然后就是换几个的问题。考虑一个类似背包的 dp:(f_{i,j}) 表示前 (i) 个中,选出总和为 (j),最多可以选几个;(g_{i,j}) 表示后 (i) 个中选出总和 (j),至少选几个。可以把 (f) 理解为保留前面的,把 (g) 理解为换来后面的。那么预处理 (f,g) 之后,枚举断点 (i),再枚举前 (i) 个保留的权值和 (j),把剩下的扔到后面去,这样离目标就相差 ([X-j,Y-j]) 了,这些就把另一半的给换过来。这里对答案产生 (maxleft(i-f_{i,j},min_{k = X-j}^{Y-j} g_{i+1,k} ight)) 的贡献。

但是不知道各位是否发现一个问题:如果两边需要移走的个数不一样怎么办?注意题目只允许交换而不能插入。但是实际上,左边多的可以直接将 (i) 换进来,这个就直接被排出统计范围(实际只需要前 (i-1) 个的和);右边多的话也可以如法炮制将 (i+1) 换出去然后扩大统计范围。

不过这个算法是 (O(N^3)) 的,我们发现贡献的式子是个滑动窗口,上一个单调队列就能 (O(N^2)) 了。一个细节就是,虽然 (A_ile 10^3),但 (sum_{i}A_i) 可能在 (10^6) 级别。事实上一个离 ([X,Y]) 很远的断点并不会对答案有什么贡献。

下面放我的代码,(O(N^2log N))(偷懒用了个 std::multiset):

const int N = 1.5e3 + 5;
const int inf = 0x3f3f3f3f;
int n, x, y, a[N];
int f[N][N], g[N][N];

int solve() {
  std::cin >> n >> x >> y;
  for (int i = 1; i <= n; i++) std::cin >> a[i];
  for (int i = 0; i <= n + 1; i++)
    memset(f[i], -inf, N << 2), memset(g[i], inf, N << 2);

  f[0][0] = 0;
  for (int i = 1; i <= n; i++)
    for (int j = 0; j < N; j++) {
      f[i][j] = f[i - 1][j];
      if (j - a[i] >= 0) f[i][j] = std::max(f[i][j], f[i - 1][j - a[i]] + 1);
    }
  g[n + 1][0] = 0;
  for (int i = n; i >= 1; i--) 
    for (int j = 0; j < N; j++) {
      g[i][j] = g[i + 1][j];
      if (j - a[i] >= 0) g[i][j] = std::min(g[i][j], g[i + 1][j - a[i]] + 1);
    }
  
  int ans = inf;
  for (int i = 1; i <= n; i++) {
    std::multiset<int> frog(g[i + 1] + x, g[i + 1] + y + 1);
    for (int j = 0; j < N; j++) {
      if (!frog.empty()) ans = std::min(ans, std::max(i - f[i][j], *frog.begin()));
      if (x - j - 1 >= 0 && x - j - 1 < N) frog.insert(g[i + 1][x - j - 1]);
      if (y - j >= 0 && y - j < N) frog.erase(frog.find(g[i + 1][y - j]));
    }
  }
  return ans >= inf / 2 ? -1 : ans;
}
原文地址:https://www.cnblogs.com/-Wallace-/p/14247364.html