Codeforces Round 897 (Div. 2)

发布时间 2023-09-13 10:26:02作者: Luckyblock

写在前面

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

简略题解。

还好没掉分。

A

令原数列中第 \(k\) 大对应 \(k\) 即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 4e4 + 10;
//=============================================================
int n, num, b[kN];
struct Data {
  int a, pos;
} a[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
bool cmp(Data fir_, Data sec_) {
  return fir_.a > sec_.a;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read();
    for (int i = 1;i <= n; ++ i) a[i] = (Data) {read(), i};
    std::sort(a + 1, a + n + 1, cmp);
    for (int i = 1; i <= n; ++ i) b[a[i].pos] = i;
    for (int i = 1; i <= n; ++ i) printf("%d ", b[i]);
    printf("\n"); 
  }
  return 0;
}

B

考虑给定串前后的对应部分原来是否相同,若不同则需要且仅需要在这两个位置中改一个,若相同则可以都不改或者都改。特别地,若 \(n\) 为奇数则中间位置改不改都行。

于是分别记录两种位置的数量 \(c_0, c_1\),显然 \(t_{c_0 + 2\times k} (0\le k\le c_1)=1\)。若原串位置为奇数则额外有 \(t_{c_0 + 2\times k + 1} = 1\)

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n, flag1, cnt0, cnt1;
char s[kN];
bool ans[kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read(); scanf("%s", s + 1);
    flag1 = n % 2, cnt0 = cnt1 = 0;
    for (int i = 1, j = n; i < j; ++ i, -- j) {
      if (s[i] == s[j]) ++ cnt0;
      else ++ cnt1;
    }
    for (int i = 0; i <= n; ++ i) ans[i] = 0;
    if (flag1) {
      for (int i = cnt1, j = 0; j <= cnt0; i += 2, ++ j) ans[i] = ans[i + 1] = 1;
    } else {
      for (int i = cnt1, j = 0; j <= cnt0; i += 2, ++ j) ans[i] = 1;
    }
    for (int i = 0; i <= n; ++ i) printf("%d", ans[i] ? 1 : 0);
    printf("\n");
  }
  return 0;
}
/*
101 011

1001 0 0011
2
2

1 0 0
1

*/

C

傻逼交互、、、看到交互就先跑了亏死。

CF 的交互很亲民,望周知。

第一次加 \(\operatorname{mex}\),之后删什么加什么。

正确性显然。

//By:Luckyblock
/*
*/
#include <cstdio>
#include <cctype>
#include <algorithm>
const int kN = 1e5 + 10;
//=============================================================
int n, mex, a[kN];
//=============================================================
inline int read() {
	int f = 1, w = 0; char ch = getchar();
	for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = - 1;
	for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + ch - '0';
	return f * w;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read();
    mex = 0;
    for (int i = 1; i <= n; ++ i) {
      a[i] = read();
      if (a[i] == mex) ++ mex;
    }

    for (int cnt = 1, del; ; ++ cnt) {
      if (cnt % 2 == 1) {
        if (cnt == 1) {
          printf("%d\n", mex);
          fflush(stdout);
          ++ mex;
          for (int i = mex; i <= n; ++ i) if (a[i] == mex) ++ mex;
        } else {
          printf("%d\n", del);
          fflush(stdout);
        }
      } else {
        del = read();
        if (del == -1) break;
        if (del == -2) return 0;
      }
    }
  }
  return 0;
}

D

套路。

先特判 \(k=1\),此时当且仅当 \(b_i=i\) 有解;若 \(k\not= 1\),则当 \(b_i=i\) 时肯定无解。

手玩一下可以发现,若对于位置 \(\{p_1, p_2, \dots, p_k\}\) 满足 \(b_{p_1} = p_2, b_{p_2} = p_3, \dots, b_{p_k} = p_1\),那么进行一次 \(l = \{p_1, p_2, \dots, p_k\}\) 的操作即可将这些位置全部修改成目标。

发现上面的情况类似于构成了一个大小为 \(k\) 的环,转换为图论模型,从节点 \(i\)\(a_i\) 连边,则没有零出度的点,构成了一片基环内向森林。

显然,若其中的所有基环内向树都是大小为 \(k\) 的环构成时皆大欢喜,分别对环中的节点代表的位置进行一次操作即可;否则若所有基环内向树中仅有大小为 \(k\) 的环时,可以考虑按照拓扑序先对链上的节点代表的位置进行操作,最后再对环中的节点代表的位置进行一次操作即可。通过上面的操作方案可以推出若出现大小不为 \(k\) 的环时无解。

于是仅需 Tarjan 检查原图中的所有环大小是否为 \(k\) 即可。总时间复杂度 \(O(n)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e6 + 10;
//=============================================================
int n, m, k, a[kN];
int edgenum, head[kN], v[kN << 1], ne[kN << 1];
int dfnnum, belnum, dfn[kN], low[kN], bel[kN], sz[kN];
std::stack <int> st;
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
void Add(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
void Tarjan(int u_) {
  low[u_] = dfn[u_] = ++ dfnnum;
  st.push(u_);
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (!dfn[v_]) {
      Tarjan(v_);
      low[u_] = std::min(low[u_], low[v_]);
    } else if (!bel[v_]) {
      low[u_] = std::min(low[u_], dfn[v_]);
    }
  }
  if (low[u_] == dfn[u_]) {
    ++ belnum;
    for (int now = 0; now != u_; st.pop()) {
      now = st.top();
      bel[now] = belnum;
      ++ sz[belnum];
    }
  }
}
void Init() {
  n = read(), k = read();
  edgenum = dfnnum = belnum = 0;
  while (!st.empty()) st.pop();
  for (int i = 1; i <= n; ++ i) {
    head[i] = dfn[i] = low[i] = bel[i] = sz[i] = 0;
  }
  for (int i = 1; i <= n; ++ i) a[i] = read(), Add(i, a[i]);
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    Init();
    int flag = 1;
    if (k == 1) {
      for (int i = 1; i <= n; ++ i) if (a[i] != i) flag = 0;
      printf("%s\n", flag ? "YES" : "NO");
      continue;
    } else {
      for (int i = 1; i <= n; ++ i) if (a[i] == i) flag = 0;
      if (!flag) {
        printf("NO\n");
        continue;
      }
    }

    for (int i = 1; i <= n; ++ i) if (!dfn[i]) Tarjan(i);
    for (int i = 1; i <= belnum; ++ i) {
      if (sz[i] > 1 && sz[i] != k) {
        flag = 0;
        break;
      }
    }
    printf("%s\n", flag ? "YES" : "NO");
  }
  return 0;
}

E1/E2

傻逼交互、、、

写完 D 就下班了亏死,要是把 E 写了就飞升了。

钦定了 \(n,k\) 均为偶数,且 \(k\le n\le k^2\),且有 \(k^2\le 2500\),显然进行不多于 \(k\) 次操作就可以覆盖整个数列。

\(k\mid n\) 皆大欢喜,进行 \(\frac{n}{k}\) 次操作恰好覆盖整个数列即可,否则考虑如何构造出最后的长度为 \(n \bmod k\) 的不完整段的异或和。

首先想到必须对 \([n - k + 1, n]\) 进行一次操作来得到这段的异或和,然后考虑通过异或去除重复部分的贡献。由于有进行一次操作后被操作的序列翻转的性质,一个显然的想法是在对 \([n - k + 1, n]\) 进行操作前先对它之前的某一段进行操作,在获得了一部分信息的同时,将重复部分翻转到 \([n - k + 1, n]\) 中,再对它进行操作来获得全部的信息。

\(s = k\times \left\lfloor\frac{n}{k}\right\rfloor + 1\),即最后不完整段的第一个位置,钦定了 \(n, k\) 为偶数,手玩一下可以发现先对 \(\left[\left\lfloor\frac{n+s}{2}\right\rfloor - k + 1, \left\lfloor\frac{n+s}{2}\right\rfloor\right]\) 进行一次操作,再对 \([n - k + 1, n]\) 进行一次操作,两次操作的结果的异或即为最后不完整段的异或和。

上述过程仅需 \(k+2\le 52\) 次操作,稳过。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
//=============================================================
int n, k;
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
int Query(int x_) {
  printf("? %d\n", x_);
  fflush(stdout);
  return read();
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T = read();
  while (T --) {
    n = read(), k = read();
    int i = 1, ans = 0;
    for (i = 1; i + k - 1 <= n; i += k) {
      ans ^= Query(i);
    }
    if (i <= n) {
      ans ^= Query((i + n) / 2 - k + 1);
      ans ^= Query(n - k + 1);
    }
    printf("! %d\n", ans);
    fflush(stdout);
  }
  return 0;
}

F

写在最后

  • 如果有轮换的性质可以考虑套路地建立图论模型。
  • CF 的交互题很亲密,望周知。