Loading

Codeforces Round 897 (Div. 2) 考试总结

前言

这次打得很好,相较于 div3 的脑残题和签到题来说,div2 的思维难度更加的大。同时还有除传统题外,其他的题型出现。比如交互题等。这次能在考场上想出三道较于之前是有很大的进步的。

赛时实况:

A B C D E1 E2 F
× × × ×

赛后改题情况:

A B C D E1 E2 F
× ×

感觉 D、F 不是很会。

A. green_gold_dog, array and permutation

Problem

给定一个长度为 \(n\) 的数列 \(a\),现在需要找到一个排列 \(b\),使得 \(a_i - b_i\) 互不相同。若有多种答案,输出其中一种即可。

Solve

首先,\(b\) 数组是一个排列,考虑排列的性质:没有重复元素,每个数只出现一次,所有数的值小于数列长度 \(n\)

现在要使 \(a_i - b_i\) 互不相同,其实就是在说 \(a_i - b_i\) 在数轴上的分布要相对分散。我们大胆猜测:最大的 \(a_i\) 匹配最小的 \(b_i\),次大的 \(a_i\) 匹配次小的 \(b_i\)\(\dots\),最小的 \(a_i\) 匹配最大的 \(b_i\)

可以用反证法证明:假如 \(\exists i<j,~c_i=c_j\),此时 \(a_i \ge a_j,b_i<b_j\)。则有:\(c_i=a_i-b_i>a_j-b_j=c_j\)。产生矛盾,故 \(\forall i<j,~c_i\not=c_j\),证毕。

时间复杂度 \(O(Tn\log n)\)

Code

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 4e4 + 10;

int T, n, a[N], p[N], Ans[N]; 

bool cmp(int x, int y) {
  return a[x] > a[y];
}

void solve() {
  read(n);
  For(i,1,n) read(a[i]), p[i] = i;
  sort(p + 1, p + n + 1, cmp);
  For(i,1,n) Ans[p[i]] = i;
  For(i,1,n) cout << Ans[i] << ' ';
  cout << '\n';
}

signed main() {
  read(T);
  while(T--) {
    solve();
  }
  return 0;
}

B. XOR Palindromes

Problem

给定一个 \(n\) 位二进制 \(01\) 串。一个数 \(x\) 是好的定义为:存在一个长度为 \(n\) 二进制 \(01\)\(l\),使得按位操作 \(s_i \oplus l_i\) 后的串为回文串。

现有长度为 \(n+1\) 答案串 \(t\),第 \(i(0\le i\le n+1)\) 位为 \(1\) 表示数字 \(i\) 是好的,为 \(0\) 表示数字 \(i\) 是不好的。求 \(t\)

Solve

首先,肯定是从小到大找答案。考虑最小的,合法(“好的”)的数 \(x\) 为多少。也就是求将原串变为回文串的最小步数。

做法是从串的中点向左右扩展。若两数不同,则耗费一步数更改其中的一位。具体是哪一位不需要管。因为我们只需要将其变为回文串,不需要关心其形态。

然后知道最小的 \(x\),考虑怎样将答案拓展。不难发现,当 \(n\) 为奇数时,介于 \(x\sim n-x\) 之间的数 \(y\) 都是“好的”;当 \(n\) 为偶数时,介于 \(x\sim n-x\) 之间的数 \(y\)\(y-x\) 为偶数都是“好的”。

证明很简单,当 \(n\) 为偶数时,每次在中点的两边对称的地方同时更改不会改变其回文的状态。也就是说,当 \(y-x\) 为偶数且 \(y\in[x,n-x]\) 时,\(y\) 是好的。奇数时同理,不过在中心改也不会改变其回文的状态,这样可以组合出 \([x,n-x]\) 的所有数,自然其中的所有数也都是“好的”。

最后模拟即可。

时间复杂度 \(O(Tn)\)

Code

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 1e5 + 10;

int T, n;

char a[N];

void solve() {
  read(n);
  For(i,1,n) cin >> a[i];
  if(n & 1) {
    int l = (1 + n) >> 1, r = l, ans1 = 0;
    while(l >= 1) {
      if(a[l] != a[r]) ans1++;
      l--, r++;
    }
    For(i,0,n) {
      if(i >= ans1 && i <= n - ans1) cout << 1;
      else cout << 0; 
    }
    cout << '\n';
  } else {
    int l = n >> 1, r = l + 1, ans1 = 0;
    while(l >= 1) {
      if(a[l] != a[r]) ans1++;
      l--, r++;
    }
    For(i,0,n) {
      if(i >= ans1 && i <= n - ans1 && (i - ans1) % 2 == 0) cout << 1;
      else cout << 0; 
    }
    cout << '\n';
  }
}

signed main() {
  read(T);
  while(T--) {
    solve();
  }
  return 0;
}

C. Salyg1n and the MEX Game

Problem

交互题。给定一个 \(n\) 个元素的集合 \(S\),元素记为 \(s_i(1 \le i \le n)\)。每一回合,你可以选择一个数 \(x(0\le x \le 10^9)\) 插入集合中,交互机会选择一个 \(\le x\) 的数,并将其从 \(S\) 中删除,保证总回合数不超过 \(2n-1\)

游戏最后的答案为 MEX,你要最大化这个结果,交互机要最小化这个结果。设 \(R\) 为你和交互机操作的最佳策略下的答案,请你规定一个策略,使最终答案至少为 \(R\)

Solve

脑瘫交互。

注意到每次交互机会选择一个 \(\le x\) 的数,并将其从 \(S\) 中删除。那么就很有可能删除掉你的 MEX。不过,你是先手。那么就能将问题转化:你先选择一个数插入,再变为交互机先手,你后手。

你先选择并插入的的数肯定是数列的 MAX,因为你要最大化 MEX。然后交互机删什么,你就填什么即可。

时间复杂度 \(O(Tn)\)

Code

#include <bits/stdc++.h>
#define int long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

const int N = 1e5 + 10;

int T, n, y, a[N];

void solve() {
  read(n);
  For(i,1,n) cin >> a[i];
  sort(a + 1, a + n + 1);
  int MEX = 0;
  For(i,1,n) {
    if(a[i] == MEX) MEX++;
  }
  cout << MEX << '\n';
  fflush(stdout);
  while(cin >> y) {
    if(y == -1) return ;
    cout << y << '\n';
    fflush(stdout);
  }
}

signed main() {
  read(T);
  while(T--) {
    solve();
  }
  return 0;
} 

这可能是我人生第一次自己做的交互题了。

D. Cyclic Operations

Problem

(咕咕咕...)

Solve

(咕咕咕...)

Code

(咕咕咕...)

E1. Salyg1n and Array (simple version)

Problem

交互题。交互库有一个长度为 \(n\) 的数列 \(a\),求其所有元素的异或和,每次你可以选择一个 \(i\),询问 \([i,i+k-1]\) 中的异或和,并将其翻转。询问不能超过 \(100\) 次。保证 \(n\)\(k\) 均为偶数

\(1 \le k \le n \le k^2 \le 2500\)

Solve

\(n\bmod k=0\) 时,直接分成 \(\frac{n}{k}\) 然后区间覆盖询问就行了。
\(n \bmod k\not=0\) 时,\([1,k\left\lfloor \dfrac{n}{k} \right\rfloor]\) 直接区间覆盖询问即可,对于 \([k\left\lfloor \dfrac{n}{k} \right\rfloor+1,n]\) ,选择 \([k\left\lfloor \dfrac{n}{k} \right\rfloor-k+2,n-k+1]\) 的所有 \(i\) 并且询问即可。

证明:原数组记为 \(a_i\),询问 \(k\left\lfloor \dfrac{n}{k} \right\rfloor-k+2\) 时,\(k\left\lfloor \dfrac{n}{k} \right\rfloor+1\) 会被计算进去,而 \([k\left\lfloor \dfrac{n}{k} \right\rfloor-k+2,k\left\lfloor \dfrac{n}{k} \right\rfloor]\) 总共会被计算两次,即两次异或操作,抵消。以此类推,最后,\([k\left\lfloor \dfrac{n}{k} \right\rfloor+1,n]\) 的所有数将会被计算一次。而 \([k\left\lfloor \dfrac{n}{k} \right\rfloor-k+2,k\left\lfloor \dfrac{n}{k} \right\rfloor]\) 将会被计算 \((n \bmod k) + 1\) 次,而 \(n \bmod k\) 为偶数,则 \((n \bmod k) + 1\) 为奇数。则原数列每个数都可以不重不漏的计算一次。

区间覆盖询问时,操作数为 \(\left\lfloor \dfrac{n}{k} \right\rfloor\),题目数据范围给出 \(1 \le k \le n \le k^2 \le 2500\),所以 \(\left\lfloor \dfrac{n}{k} \right\rfloor \le k \le n \le 50\),所以询问数最多不会超过 \(50\) 次。而 \((n \bmod k) + 1 = n-\left\lfloor \dfrac{n}{k} \right\rfloor+1\le 50\),所以总询问数不会超过 \(100\) 次,可以通过本题。

时间复杂度 \(O(Tn)\)

Code

#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

int n, T, k, res;

int ask(int i) {
  cout << "? " << i << '\n';
  fflush(stdout);
  int x; cin >> x;
  return x;
}

void solve() {
  res = 0; 
  cin >> n >> k;
  int i;
  for (i = 1; i + k - 1 <= n; i += k) {
    res ^= ask(i);
  } 
  if(n % k == 0) {
    cout << "! " << res << '\n';
    fflush(stdout);
  } else {
    res ^= ask(i - k + (n % k) / 2);
    res ^= ask(n - k + 1);
    cout << "! " << res << '\n';
    fflush(stdout);
  }
  return ;
}

signed main() {
  cin >> T;
  while(T--) {
    solve(); 
  }
  return 0;
}

E2. Salyg1n and Array (hard version)

Problem

交互题。交互库有一个长度为 \(n\) 的数列 \(a\),求其所有元素的异或和,每次你可以选择一个 \(i\),询问 \([i,i+k-1]\) 中的异或和,并将其翻转。询问不能超过 \(57\) 次。保证 \(n\)\(k\) 均为偶数

Solve

我们发现 E1 的程序不高效率的原因在于它有很多的冗余运算。比如,最后的剩余段会被询问 \((n \bmod k) + 1\) 次,这是很不高效的。我们发现前面的区间覆盖询问 \(\left\lfloor \dfrac{n}{k} \right\rfloor\) 次是无法避免的。这时候,留给我们的剩余询问数就只剩下了 \(7\) 个。所以我们要高效的处理剩余段的询问。

我们考虑将 \([k\left\lfloor \dfrac{n}{k} \right\rfloor+1,n]\) 折半,一半的长度记为 \(l\)。将左半段区间和 \([k\left\lfloor \dfrac{n}{k} \right\rfloor+1-l,k\left\lfloor \dfrac{n}{k} \right\rfloor]\) 一起询问,然后原数组 \([k\left\lfloor \dfrac{n}{k} \right\rfloor+1-l,k\left\lfloor \dfrac{n}{k} \right\rfloor]\) 将会被翻转到折半区间的左半段区间,再与右半段区间询问一次。这样,原本 \([k\left\lfloor \dfrac{n}{k} \right\rfloor+1,n]\) 将会被询问一次。\([k\left\lfloor \dfrac{n}{k} \right\rfloor+1-l,k\left\lfloor \dfrac{n}{k} \right\rfloor]\) 总共会被询问 \(3\) 次,贡献一次答案。刚好将原数组的所有元素包含。

这样最多只需要询问 \(52\) 即可,可以通过此题。

Code

#include <bits/stdc++.h>
#define ll long long
#define H 19260817
#define rint register int
#define For(i,l,r) for(rint i=l;i<=r;++i)
#define FOR(i,r,l) for(rint i=r;i>=l;--i)
#define MOD 1000003
#define mod 1000000007

using namespace std;

namespace Read {
  template <typename T>
  inline void read(T &x) {
    x=0;T f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
  }
  template <typename T, typename... Args>
  inline void read(T &t, Args&... args) {
    read(t), read(args...);
  }
}

using namespace Read;

void print(int x){
  if(x<0){putchar('-');x=-x;}
  if(x>9){print(x/10);putchar(x%10+'0');}
  else putchar(x+'0');
  return;
}

int n, T, k, res;

int ask(int i) {
  cout << "? " << i << '\n';
  fflush(stdout);
  int x; cin >> x;
  return x;
}

void solve() {
  res = 0; 
  cin >> n >> k;
  int i;
  for (i = 1; i + k - 1 <= n; i += k) {
    res ^= ask(i);
  } 
  if(n % k == 0) {
    cout << "! " << res << '\n';
    fflush(stdout);
  } else {
    res ^= ask(i - k + (n % k) / 2);
    res ^= ask(n - k + 1);
    cout << "! " << res << '\n';
    fflush(stdout);
  }
  return ;
}

signed main() {
  cin >> T;
  while(T--) {
    solve(); 
  }
  return 0;
}

F. Most Different Tree

Problem

(咕咕咕...)

Solve

(咕咕咕...)

Code

(咕咕咕...)

posted @ 2023-09-14 15:34  Daniel_yzy  阅读(177)  评论(0编辑  收藏  举报