Codeforces Round 963 (Div. 2)

写在前面

比赛地址:https://codeforces.com/contest/1993

妈的睡到 22:35 整点起床,刚下床就开了妈的太刺激!

为了保证队长当前是 1k9 这个事实不变方便劝诱新大神,于是上小号了呃呃,D 调出来了不是太烂感觉暑假肯定能把小号也打上紫嘻嘻

唉反正小号随便打了呃呃

置顶广告:中南大学 ACM 集训队绝赞招新中!

有信息奥赛基础,获得 NOIP 省一等奖并达到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我与校队队长联系,免选拔直接进校集训队参加区域赛!

没有达到该水平但有志于 XPCX 赛事请关注每学年开始的 ACM 校队招新喵!

到这个时候了还缺队友实在不妙!求求求求快来个大神带我呜呜呜呜

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; std::cin >> n;
    std::string s; std::cin >> s;
    int ans = 0, cnt1 = 0, cnt[4] = {n, n, n, n};
    for (auto ch: s) {
      if (ch == '?') ++ cnt1;
      else if (cnt[ch - 'A']) -- cnt[ch - 'A'], ++ ans;
    }
    std::cout << ans << "\n";
  }
  return 0;
}

B

模拟。

呃呃反正我是模拟写的,应该有更简单的直接判定的方法。

先特判一开始就合法。然后考虑到奇数+偶数=奇数,则考虑如何将所有偶数变为奇数。观察样例可知,操作次数不会超过偶数个数 +1 次,因为可以选择任意奇数与最大的偶数操作两次,可以造出一个比任何偶数都大的奇数,再使用该奇数操作即可。

则操作次数仅可能为偶数个数,或偶数个数+1。

考虑答案能否取到下界。操作次数为 \(O(n)\) 级别则可直接模拟。显然每次操作一定会使用当前最大的奇数进行操作,考虑记录所有偶数并升序排序,然后按顺序使用最大的奇数对所有数操作,并更新最大奇数即可。若在此过程中较小值一直为偶数一方则可取到下界。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define int long long
const int kInf = 1e9 + 2077;
//=============================================================
//=============================================================
//=============================================================
signed 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; std::cin >> n;
    std::vector<int> even;
    int cnt[2] = {0}, maxodd = 0;;
    for (int i = 1; i <= n; ++ i) {
      int x; std::cin >> x;
      if (x % 2 == 1 && x > maxodd) maxodd = x;
      if (x % 2 == 0) even.push_back(x);
      ++ cnt[x % 2];
    }
    if (cnt[0] == 0 || cnt[1] == 0) {
      std::cout << 0 << "\n";
      continue;
    }
    std::sort(even.begin(), even.end());
    
    int flag = 0;
    for (auto x: even) {
      if (x < maxodd) maxodd = x + maxodd;
      else flag = 1;

      if (x > even.back()) break;
    }
    std::cout << cnt[0] + flag << "\n";
  }
  return 0;
}
/*
1
5
999999996 999999997 999999998 999999999 1000000000
*/

C

乱搞。

感觉有一万种方法能过呃呃,我这种属于是最烂的。

发现有 \(k\le n\),则第一次全部灯亮一定出现在 \(\operatorname{max}_i a_i\) 时刻之后,所有灯变化次数不超过 1 次的时间范围内。于是直接记录每盏灯下一个状态变化的时刻,考虑时刻的变化大力模拟了呃呃,推荐指数不推荐。

复杂度 \(O(n)\) 级别。

正确性应该没错吧大概不会被叉吧呃呃呃,反正 system testing 是过了哈哈不过这个 system testing \(O(nk)\) 都放过去了真是不好说、、、

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, k, a[kN];
std::vector<int> on, off;
//=============================================================
//=============================================================
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 >> k;
    on.clear(), off.clear();
    for (int i = 1; i <= n; ++ i) std::cin >> a[i];
    std::sort(a + 1, a + n + 1);
    for (int i = 1; i <= n; ++ i) {
      int d = a[n] - a[i], t = d / k;
      if (t % 2 == 0) on.push_back(a[n] + k - d % k);
      else off.push_back(a[n] + k - d % k);
    }
    if (on.size() == n) {
      std::cout << a[n] << "\n";
      continue;
    }
    
    std::sort(on.begin(), on.end());
    std::sort(off.begin(), off.end());
    int ans = -1, szon = on.size(), szoff = off.size();
    int realszon = szon, realszoff = szoff;
    
    for (int now = 1, i = 0, j = 0, t = a[n]; now <= n; ++ now) {
      if (i == szon) {
        t = off[j];
        on.push_back(off[j] + k);
        ++ szon;
        ++ realszon, -- realszoff;
        ++ j;
      } else if (j == szoff) {
        t = on[i];
        off.push_back(on[i] + k);
        ++ szoff;
        ++ realszoff, -- realszon;
        ++ i;
      } else {
        if (on[i] < off[j]) {
          t = on[i];
          off.push_back(on[i] + k);
          ++ szoff;
          ++ realszoff, -- realszon;
          ++ i;
        } else {
          t = off[j];
          on.push_back(off[j] + k);
          ++ szon;
          ++ realszon, -- realszoff;
          ++ j;
        }
      }

      int ok = 1;
      if (i < szon) if (on[i] == t) ok = 0;
      if (j < szoff) if (off[j] == t) ok = 0;
      if (ok && realszon == n) {
        ans = t; 
        break;
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}

D

二分答案,最长上升子序列。

令下标从 0 开始编号。每次删保证删除的是长度为 \(k\) 的子区间,即每次删除的数的下标 \(\bmod k\) 恰好构成了 \(\bmod k\) 的一个剩余系,手玩下很容易发现,若最终剩下 \(n'\) 个数,其下标 \(\bmod k\) 按顺序一定为 \(0, 1, \cdots, n'-1\)

中位数为 \(x\) 的必要条件是数列中不小于 \(x\) 的数不少于 \(\left\lceil\frac{n'}{2}\right\rceil\) 个,发现答案有单调性,考虑二分答案并每次检查枚举量 \(\operatorname{lim}\) 是否合法,即检查是否存在某个子序列 \(L = \{a_{l_0}, a_{l_1}, \cdots, a_{l_{n'-1}}\}\),有 \(\forall 0\le i\le n'-1, l_i\bmod k = i, l_{i - 1}< l_i\),且 \(L\) 中有不少于 \(\left\lceil\frac{n'}{2}\right\rceil\) 个不小于 \(\operatorname{lim}\) 的数。

发现 \(l_{i - 1}< l_i\) 等价于选择的数的下标 \(i\bmod k\) 一个 \(0\sim n'-1\) 的最长上升子序列。则选择不小于 \(\operatorname{lim}\) 的数时,仅需保证它们的下标 \(i\bmod k\) 的值是严格单调递增的,即可保证存在一种操作方案使它们出现在同一个 \(L\) 中。

于是每次 check 时,考虑对 \(a\) 中所有不小于 \(\operatorname{lim}\)\(i\bmod k\le n'-1\) 的位置 \(i\),记录 \(i\bmod k\) 的值并求最长上升子序列,其长度 \(\operatorname{len}\) 即为子序列 \(L\) 中不小于 \(\operatorname{lim}\) 的数的最大数量,检查是否有 \(\operatorname{len}\ge \left\lceil\frac{n'}{2}\right\rceil\) 即可。

总时间复杂度 \(O(n\log v\log n)\) 级别,常数非常小大概不会被叉哈哈。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
//=============================================================
int n, k, nn;
LL a[kN], ans;
//=============================================================
namespace Bit {
  #define lowbit(x) ((x)&(-x))
  const int kLim = kN << 1;
  int lim, nowtime, t[kLim], tim[kLim];
  void Init(int lim_) {
    lim = lim_, ++ nowtime;
  }
  void Insert(int pos_, int val_) {
    for (int i = pos_; i <= lim; i += lowbit(i)) {
      if (tim[i] != nowtime) t[i] = 0, tim[i] = nowtime;
      t[i] = std::max(t[i], val_);
    }
  }
  int Max(int pos_) {
    int ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) {
      if (tim[i] != nowtime) t[i] = 0, tim[i] = nowtime;
      ret = std::max(ret, t[i]);
    }
    return ret;
  }
}
bool check(LL lim_) {
  std::vector<int> pos {-1};
  for (int i = 0; i < n; ++ i) {
    if (a[i] >= lim_ && i % k < nn) 
      pos.push_back(i % k + 1);
  }
  int len = 0;
  Bit::Init(k + 1);
  for (int i = 1, sz = pos.size(); i < sz; ++ i) {
    int ret = Bit::Max(pos[i] - 1) + 1;
    Bit::Insert(pos[i], ret);
    len = std::max(len, ret);
  }
  return len >= (nn / 2 + 1);
}
//=============================================================
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 >> k;
    for (int i = 0; i < n; ++ i) std::cin >> a[i];
    if (n % k == 0) nn = k;
    else nn = (n % k);

    ans = 0;
    for (LL l = 1, r = 1e9; l <= r; ) {
      LL mid = (l + r) >> 1ll;
      if (check(mid)) {
        l = mid + 1;
        ans = mid;
      } else {
        r = mid - 1;
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
4 3
3 9 9 2
5 3
3 2 5 6 4
7 1
5 9 2 6 5 4 6
8 2
7 1 2 6 8 3 4 5
4 5
3 4 5 6
*/

E

结论,异或,状压 DP

先考虑一维的情况。

若只有一维,每次操作的结果和 [AGC016D] XOR Replace 是一样的。对 \(a_i\) 进行一次操作相当于令 \(a_i:=\oplus_{1\le i\le n} a_i\),再对 \(j\) 进行一次操作相当于令 \(a_j:= a_i\)

则题意等价于有一个长度为 \(n + 1\) 的数列 \(a\)\(a_{n + 1} = \oplus_{1\le i\le n} a_i\),可以任意交换 \(a_i\)\(a_{n + 1}\),对于 \(a_1\sim a_n\) 求相邻两项差的绝对值之和的最小值。

发现数据范围很小,且贡献仅与相邻元素有关,考虑状压 DP 构造数列,记 \(f_{s, i}\) 表示当前填入的数列元素集合为 \(s\),填入的最后一个数是 \(a_i\) 时美丽值的最小值。初始化 \(f_{s, i} = \infin, f_{\{i\}, i} = 0\),则有显然的转移:

\[\forall 1\le i, j\le n + 1, i\not= j, i\in s,\ \ f_{s, i}\rightarrow f_{s \cup \{j\}, j} + |a_{i} - a_{j}| \]

记全集为 \(S\),答案即为:

\[\min_{1\le i,j\le n+1, i\not= j} f_{S - \{i\}, j} \]

总时间复杂度 \(O(n^2 2^n)\) 级别。

扩展到两维,发现若先进行一次行操作再进行一次列操作,等价于将交点位置修改为整个矩阵的异或和。于是考虑扩展上述做法,题意等价于有一个大小为 \((n + 1)\times (m + 1)\) 的矩阵,第 \(n+1\) 行为各列的异或和,第 \(m+1\) 列为各行的异或和(\((n + 1, m + 1)\) 即整个矩阵异或和),每次操作可以交换两行/两列,求左上角 \(n\times m\) 矩阵的美丽值的最小值。

考虑预处理任意两行/两列相邻时的贡献。发现两维的贡献是独立的,不同维的交换并不影响另一维的贡献。发现若枚举了哪一行被放在了 \(n+1\) 行上,则对列的贡献的计算就可以直接套用一维的做法了。于是考虑转移时处理 \(\operatorname{sum}(i, j)\) 表示将第 \(i\) 行第 \(j\) 列时的最小美丽值,取最小值即可。

在一维做法的基础上仅需再多枚举一维即可,总时间复杂度 \(O(n^2m 2^n + nm^2 2^m) \approx O(n^3 2^n)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 16 + 2;
const int kS = (1 << 16) + 10;
const LL kInf = 1e18;
//=============================================================
int n, m, a[kN][kN];
LL ans, all, row[kN][kN], col[kN][kN], f[kS][kN];
LL sum[kN][kN];
//=============================================================
void init() {
  a[n + 1][m + 1] = 0;
  for (int i = 1; i <= n; ++ i) {
    a[i][m + 1] = 0;
    for (int j = 1; j <= m; ++ j) {
      a[i][m + 1] ^= a[i][j];
      a[n + 1][m + 1] ^= a[i][j];
    }
  }
  for (int j = 1; j <= m; ++ j) {
    a[n + 1][j] = 0;
    for (int i = 1; i <= n; ++ i) {
      a[n + 1][j] ^= a[i][j];
    }
  }
  for (int i = 1; i <= n + 1; ++ i) {
    for (int j = 1; j <= n + 1; ++ j) {
      row[i][j] = 0;
      for (int k = 1; k <= m + 1; ++ k) 
        row[i][j] += abs(a[i][k] - a[j][k]);
    }
  }
  for (int i = 1; i <= m + 1; ++ i) {
    for (int j = 1; j <= m + 1; ++ j) {
      col[i][j] = 0;
      for (int k = 1; k <= n + 1; ++ k)
        col[i][j] += abs(a[k][i] - a[k][j]);
    }
  }
}
void DP() {
  for (int i = 1; i <= n + 1; ++ i) {
    for (int j = 1; j <= m + 1; ++ j) {
      sum[i][j] = 0;
    }
  }

  all = (1 << (n + 1));
  for (int lst = 1; lst <= m + 1; ++ lst) {
    for (int s = 1; s < all; ++ s) {
      for (int i = 1; i <= n + 1; ++ i) {
        f[s][i] = kInf;
      }
    }
    for (int i = 1; i <= n + 1; ++ i) f[1 << (i - 1)][i] = 0;
    for (int s = 1; s < all; ++ s) {
      for (int i = 1; i <= n + 1; ++ i) {
        if ((s >> (i - 1) & 1) == 0) continue;
        for (int j = 1; j <= n + 1; ++ j) {
          if (i == j || (s >> (j - 1) & 1)) continue;
          f[s | (1 << (j - 1))][j] = std::min(f[s | (1 << (j - 1))][j], 
                                              f[s][i] + row[i][j] - abs(a[i][lst] - a[j][lst]));
        }
      }
    }
    for (int i = 1; i <= n + 1; ++ i) {
      LL minf = kInf;
      for (int j = 1; j <= n + 1; ++ j) {
        if (i == j) continue;
        minf = std::min(minf, f[(all - 1) ^ (1 << (i - 1))][j]);
      }
      sum[i][lst] += minf;
    }
  }
  
  all = (1 << (m + 1));
  for (int lst = 1; lst <= n + 1; ++ lst) {
    for (int s = 1; s < all; ++ s) {
      for (int i = 1; i <= m + 1; ++ i) {
        f[s][i] = kInf;
      }
    }
    for (int i = 1; i <= m + 1; ++ i) f[1 << (i - 1)][i] = 0;
    for (int s = 1; s < all; ++ s) {
      for (int i = 1; i <= m + 1; ++ i) {
        if ((s >> (i - 1) & 1) == 0) continue;
        for (int j = 1; j <= m + 1; ++ j) {
          if (i == j || (s >> (j - 1) & 1)) continue;
          f[s | (1 << (j - 1))][j] = std::min(f[s | (1 << (j - 1))][j], 
                                              f[s][i] + col[i][j] - abs(a[lst][i] - a[lst][j]));
        }
      }
    }
    for (int i = 1; i <= m + 1; ++ i) {
      LL minf = kInf;
      for (int j = 1; j <= m + 1; ++ j) {
        if (i == j) continue;
        minf = std::min(minf, f[(all - 1) ^ (1 << (i - 1))][j]);
      }
      sum[lst][i] += minf;
    }
  }

  ans = kInf;
  for (int i = 1; i <= n + 1; ++ i) {
    for (int j = 1; j <= m + 1; ++ j) {
      ans = std::min(ans, sum[i][j]);
    }
  }
}
//=============================================================
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];
      }
    }
    init();
    DP();
    std::cout << ans << "\n";
  }
  return 0;
}
/*
1
1 2
1 3
*/

F1

待补。

考虑镜像操作,将移动范围扩大到 \(2w \times 2h\) 的区域,并将区域平移到第一象限,则仅需考虑统计经过 \((k_1\times 2w,k_2\times 2h)\) 多少次,\(k_1, k_2\) 任取。

写在最后

学到了什么:

  • D:考虑剩余系。

你说的对按照常理来说现在又是夹带私货环节:

结尾广告:中南大学 ACM 集训队绝赞招新中!

有信息奥赛基础,获得 NOIP 省一等奖并达到 Codeforces rating 1900+ 或同等水平及以上者,可以直接私聊我与校队队长联系,免选拔直接进校集训队参加区域赛!

没有达到该水平但有志于 XPCX 赛事请关注每学年开始的 ACM 校队招新喵!

到这个时候了还缺队友实在不妙!求求求求快来个大神带我呜呜呜呜

posted @ 2024-08-05 02:00  Luckyblock  阅读(213)  评论(0编辑  收藏  举报