Codeforces Round #681


写在前面

Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020 - Final)

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

选择去厕所门口嗨而不是把 SB E 写完的 Lb 真是人间之鉴。
以及这场直接是暴力大赛= =


A

Link

\(t\) 组询问,每次给定整数 \(n\),构造一个长度为 \(n\) 的数列 \(a\),满足:

  • \(\forall 1\le i\le n,\ 1\le a_i\le 4n\)
  • \(\forall 1\le i,j\le n,\ \gcd(i,j)\not= 1,\ a_i\nmid a_j,\ a_j\nmid a_i\)

\(1\le t,n\le 100\)
2S,256MB,SPJ。

贪心。

一种容易想到的合法构造方案是:

\[4n,\ 4n-2,\ ,\cdots, 2n+2 \]

//知识点:贪心
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
//=============================================================
//=============================================================
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 Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
//=============================================================
int main() { 
  int t = read();
  while (t --) {
    int n = read();
    for (int i = 4 * n; n; i -= 2) {
      printf("%d ", i);
      -- n;
    }
    printf("\n");
  }
  return 0; 
}

B

Link

\(t\) 组数据,每次给定一长度为 \(n\)\(01\) 序列,给定两种操作:

  1. 选择一段连续的 \(1\),花费 \(a\) 的代价将它们全变为 \(0\)
  2. 花费 \(b\) 的代价,使一个 \(0\) 变为 \(1\)

求使整个序列都变成 \(0\) 的最小代价。
\(1\le t\le 10^5\)\(1\le \sum n\le 10^5\)\(1\le a,b\le 10^3\)
2S,256MB。

贪心。

如果有两段相邻的连续的 \(1\),削除它们的方法有两种。
一是进行 2 次操作 1,花费为 \(2a\)
二是先进行多操作 2 使它们相连,再进行操作 1,花费为 \(a+kb\)

于是从左向右贪心,若两段 1 的距离乘 \(b\) 小于 \(a\) 则用第二种方法,否则用第一种。

注意实现。

//知识点:贪心
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
char s[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;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
//=============================================================
int main() { 
  int t = read();
  while (t --) {
    int a = read(), b = read();
    int n, ans = 0, cnt0 = a; //赋初值,处理序列开头是一堆 0 的情况。
    scanf("%s", s + 1);
    n = strlen(s + 1);

    for (int i = 1; i <= n; ++ i) {
      if (s[i] == '1' && s[i - 1] != '1') {
        ans += std::min(cnt0 * b, a);
        cnt0 = 0;
        continue ;
      }
      cnt0 += (s[i] == '0');
    }
    printf("%d\n", ans);
  }
  return 0; 
}
/*
1
1 1
11111
*/

C

Link

\(t\) 组数据,每次给定 \(n\) 个物品。
对于第 \(i\) 个物品,可以选择快递运输,花费 \(a_i\) 时间。或上门自取,花费 \(b_i\) 时间。
快递是同时出发的,上门自取时间是累加的。
求最小的总时间花费。
\(1\le t\le 2\times 10^5\)\(1\le \sum n\le 2\times 10^5\)\(1\le a_i,b_i\le 10^9\)
2S,256MB。

二分答案。

一开始我尝试把题意抽象成一堆具有 2 个属性的物品,但发现丢失了时间这个重要条件后,做法就不显然了,于是改了回去= =

显然二分答案,枚举总时间花费 \(mid\)
对于一个物品 \((a_i,b_i)\),若快递运输的时间 \(\le mid\),则使用快递运输,否则上门自取。
检查上门自取时间是否不大于 \(mid\) 即可。

//知识点:二分答案
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 2e5 + 10;
const LL kInf = 1e15 + 2077;
//=============================================================
int n, a[kN], b[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;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
bool Check(LL lim_) {
  LL sum = 0;
  for (int i = 1; i <= n; ++ i) {
    if (1ll * a[i] <= lim_) continue ;
    sum += 1ll * b[i];
  }
  return sum <= lim_;
}
//=============================================================
int main() { 
  int t = read();
  while (t -- ){
    n = read();
    for (int i = 1; i <= n; ++ i) a[i] = read();
    for (int i = 1; i <= n; ++ i) b[i] = read();
    LL ans = 0;
    for (LL l = 1, r = kInf; l <= r; ) {
      LL mid = (l + r) >> 1;
      if (Check(mid)) {
        ans = mid;
        r = mid - 1;
      } else {
        l = mid + 1;
      }
    }
    printf("%lld\n", ans);
  }
  return 0; 
}

D

Link

\(t\) 组数据,每次给定一长度为 \(n\) 的数列 \(a\),有两种操作:

  1. 选择任意整数 \(k\ (1\le k\le n)\),使 \(a_1 \sim a_k\) 减去 \(1\)
  2. 选择任意整数 \(k\ (1\le k\le n)\),使 \(a_k \sim a_n\) 减去 \(1\)

求经过任意次操作后,能否使得所有数全变为 \(0\)
\(1\le t,\sum n\le 3\times 10^4\)\(1\le a_i\le 10^6\)
2S,256MB。

差分,贪心。

区间操作先考虑差分,设 \(a\) 的差分数组为 \(b\),有 \(b_i = a_i - a_{i-1}\)
问题变为能否令 \(b_1\sim b_n\) 全变为 \(0\)

考虑两种操作对差分数组的影响。
操作 1 令 \(b_1 - 1\)\(b_{k+1} +1\),操作 2 令 \(b_k - 1\)\(b_{n+1} + 1\)
\(b_{n+1}\) 的操作不会影响答案,则可以通过若干次操作 2 使 \(b_2 \sim b_n\) 中所有大于 0 的元素变为 0。
再考虑操作 1,显然应通过操作 1 将所有不大于 0 的 \(b_i\) 调整为 0。若 \(b_1\sim b_n\) 中所有小于 0 的元素的绝对值之和是否大于 \(a_1\),显然无解。若小于 0,则可以通过操作 2 将 \(a_1\) 再调整为 0。

于是仅需检查 \(b_1\sim b_n\) 中所有小于 0 的元素的绝对值之和是否不大于 \(a_1\) 即可。

//知识点:差分,贪心
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 3e4 + 10;
//=============================================================
int n, a[kN], b[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;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
//=============================================================
int main() { 
  int t = read();
  while (t --) {
    n = read();
    for (int i = 1; i <= n; ++ i) {
      a[i] = read();
      b[i] = a[i] - a[i - 1];
      if (b[i] < 0) a[1] += b[i];
    }
    printf("%s\n", a[1] >= 0 ? "YES" : "NO");
  }
  return 0; 
}

E

Link

对于一 \(1\sim n\) 的排列,定义其排名为在 \(1\sim n\) 的全排列中,它的字典序的排名。
有一 \(1\sim n\) 的排列 \(a\),初始时有 \(\forall 1\le i\le n,\ a_i = i\)。有 \(m\) 次操作:

  1. 查询 \([l,r]\) 的区间和。
  2. 令排列 \(a\) 的排名增加 \(x\)

\(2\le n,m\le 2\times 10^5\)\(1\le x\le 10^5\),保证 \(\sum x< n!\)
4S,256MB。

暴力。

发现操作 2 不好维护,但观察数据范围,发现有 \(\sum x\le 2\times 10^{10}\),且有 \(14!>2\times 10^{10}\)
则经过若干次操作 2 后,只有最后 \(14\) 个位置会有变化。

则对前 \(n-14\) 个位置维护一个前缀和,后 \(14\) 个位置独立出来。
查询操作根据 \([l,r]\) 是否包含 \(n-14\) 分类讨论,前面部分用前缀和查询,后面直接暴力。
修改操作暴力改后 \(14\) 个位置即可。

关于怎么暴力维护:
如果了解康托展开的话可以直接逆展开一下。
如果嫌麻烦可打表找下规律:

1234
1243
----
1324
1342
----
1423
1432
--------------------------
2134
2143
----
2314
2341
----
2413
2431
--------------------------
3124
3142
----
3214
3241
----
3412
3421
--------------------------
4123
4132
----
4213
4231
----
4312
4321

\(3!=6\)\(2!=2\),规律非常显然,预处理阶乘,从字典序中从大到小减去阶乘,即可得到每一位的元素。
注意实现时的细节。

//知识点:暴力
/*
By:Luckyblock
爆炸阶乘!
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, m, a[kN], tmp[20];
LL sumx = 1, fac[20], sum[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;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Prepare() {
  n = read(), m = read();
  fac[0] = 1;
  for (int i = 1; i <= n; ++ i) {
    if (i <= 14) fac[i] = 1ll * fac[i - 1] * i;
    sum[i] = 1ll * sum[i - 1] + i;
    a[i] = i;
  }
}
LL Query(int l_, int r_) {
  LL ret = 0;
  if (r_ - l_ <= 14) { //可以直接暴力
    for (int i = l_; i <= r_; ++ i) ret += 1ll * a[i];
    return ret;
  }
  if (r_ < n - 14) { //特判
    ret = sum[r_] - sum[l_ - 1];
  } else { //暴力
    ret += sum[n - 15] - sum[l_ - 1];
    for (int i = n - 14; i <= r_; ++ i) ret += 1ll * a[i];
  }
  return ret;
}
void Modify() {
  int lim = std::min(14, n); //注意枚举上界
  LL x = sumx;
  bool vis[20] = {0};

  for (int i = lim - 1; i >= 1; -- i) {
    for (int j = 1; j <= lim; ++ j) { //不断减去 i!,确定元素。
      if (vis[j]) continue;
      if (x > fac[i]) {
        x -= fac[i];
        continue;
      }
      tmp[lim - i] = j; //填入
      vis[j] = true;
      break;
    }
  }
  for (int j = 1; j <= lim; ++ j) { //最后一个元素
    if (! vis[j]) {
      tmp[lim] = j;
      break;  
    }
  }
  
  if (n <= 14) { //暴力改
    for (int i = 1; i <= n; ++ i) a[i] = tmp[i];
  } else {
    for (int i = 1; i <= 14; ++ i) {
      a[n - 14 + i] = tmp[i] + n - 14;
    }
  }
  // printf("\n\n\n");
  // for (int i = 1; i <= n; ++ i) printf("%d ", a[i]);
  // printf("\n\n\n");
}
//=============================================================
int main() { 
  Prepare();
  while (m --) {
    int opt = read();
    if (opt == 1) {
      int l = read(), r = read();
      printf("%lld\n", Query(l, r));
    } else {
      sumx += 1ll * read(); //更新字典序
      Modify();
    }
  }
  return 0; 
}
/*
16 3
1 1 16
2 1000000
1 15 16
*/

F

Link

\(t\) 组数据,每次给定一 \(1\sim n\) 的排列 \(a\),一长度为 \(m\) 的数列 \(b\),满足 \(b\) 中元素不重复,且 \(\forall 1\le i\le m,\ \exists 1\le j\le n,\ b_i = a_j\)
需要进行 \(m\) 次操作,每次操作从数列 \(a\) 中选择一个下标 \(t\),选择 \(a_{t-1}\)\(a_{t+1}\) 加入 \(c\) 数列的末尾,然后删除 \(a_t\)
求有多少种操作方案,能使数列 \(c\) 等于 数列 \(b\),答案对 \(998244353\) 取模。
\(1\le t\le 10^5\)\(1\le m\le n\le 2\times 10^5\)\(1\le \sum n\le 2\times 10^5\)
2S,512MB。

暴力。

\(a,b\) 中的数都是不重的,则对于 \(b\) 中所有数,用 \(b_i\)\(a\) 中的位置替换它。

把这个操作转化一下,等价于每次在 \(a\) 中选择一个 \(b\) 中的数,选择左右两侧的一个删除,在将该数加入到 \(c\) 的末尾。
考虑顺序枚举 \(b\) 加数,设当前加到 \(b_i\),讨论 \(a_{b_i-1}\)\(a_{b_i+1}\) 的情况。

  • \(a_{b_i -1}\)\(a_{b_i + 1}\) 均在 \(b\) 中,则不能删去任何一个,无解。
  • \(a_{b_i - 1}\)\(a_{b_i + 1}\) 中仅有一个不在 \(b\) 中,显然应删除另一个,删除的方案数为 1。
  • \(a_{b_i -1}\)\(a_{b_i + 1}\) 均不在 \(b\) 中,则可以删除任意一个。因为删除后 \(a_{b_i}\) 也可以删除,对原数列的影响相同。则删除的方案数为 2。

根据上述情况,用链表模拟删除即可。总复杂度 \(O\left(\sum n\right)\)
注意特判一些边界情况。
不能用 memset,否则 TLE 警告。

//知识点:暴力
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define LL long long
const int kN = 2e5 + 10;
const int mod = 998244353;
//=============================================================
struct Data {
  int val, l, r;
} a[kN];
int n, m, ans, b[kN], posa[kN];
bool tag[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;
}
void Chkmax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
void Chkmin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Delete(int pos_) {
  if (a[pos_].l) a[a[pos_].l].r = a[pos_].r;
  if (a[pos_].r) a[a[pos_].r].l = a[pos_].l;
}
void Init() {
  n = read(), m = read();
  ans = 1;
  for (int i = 0; i <= n + 1; ++ i) {
    a[i] = (Data) {0, 0, 0};
    posa[i] = tag[i] = 0;
  }
  tag[0] = tag[n + 1] = true;

  for (int i = 1; i <= n; ++ i) {
    a[i] = (Data) {read(), i - 1, i + 1};
    posa[a[i].val] = i;
  }
  for (int i = 1; i <= m; ++ i) {
    b[i] = read();
    tag[b[i]] = i;
  }
}
//=============================================================
int main() { 
  int t = read();
  while (t --) {
    Init();
    for (int i = 1; i <= m; ++ i) {
      int val = b[i], pos = posa[val];
      if (tag[a[a[pos].l].val] && tag[a[a[pos].r].val]) {
        ans = 0;
        break ;
      } else if (tag[a[a[pos].l].val]) {
        Delete(a[pos].r);
      } else if (tag[a[a[pos].r].val]) {
        Delete(a[pos].l);
      } else {
        ans = 2ll * ans % mod;
        Delete(a[pos].r ? a[pos].r : a[pos].l);
      }
      tag[val] = false;
    }
    printf("%d\n", ans);
  }
  return 0; 
}

总结

  • 区间操作先考虑差分。
  • 注意观察数据范围,结合数据范围和题目性质,能够得到一些结论。
  • 多测直接 memset 清空数组的全部必定 TLE,应用 for 清空,用到多少清空多少。
  • 注意链表的边界情况,注意特判。
posted @ 2020-11-21 15:13  Luckyblock  阅读(52)  评论(0编辑  收藏  举报