【CodeForces】CodeForces Round #507 (Div. 1) 题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_39972971/article/details/82595840

【比赛链接】

【题解链接】

**【A】**Timetable

【思路要点】

  • 首先,若存在任何一组合法解,有 b i a i + t   ( 1 i N )
  • 对于每一个 x i ,应当满足 x i i ,   b j a j + 1 + t   ( i j < x i ) ,   b x i < a x i + 1 + t     o r     x i = N
  • 用差分+前缀和处理出所有需要满足 b i a i + 1 + t 的位置,并且在综合 b i a i + t b i > b i 1 的限制下构造出使得每一个 b i 尽可能小的解,检验是否满足 b x i < a x i + 1 + t     o r     x i = N
  • 若满足,方案即为构造出的 b 数组,否则问题无解。
  • 时间复杂度 O ( N 2 )

【代码】


#include<bits/stdc++.h>

using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
  for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
int n, x[MAXN], cnt[MAXN][2];
ll t, a[MAXN], b[MAXN];
int main() {
  read(n), read(t);
  for (int i = 1; i <= n; i++)
      read(a[i]);
  for (int i = 1; i <= n; i++) {
      read(x[i]);
      if (x[i] < i) {
          printf("No\n");
          return 0;
      }
      chkmax(b[x[i]], a[i] + t);
      cnt[1][0]++, cnt[i][0]--;
      cnt[x[i] + 1][0]++, cnt[n + 1][0]--;
      cnt[i][1]++, cnt[x[i]][1]--;
  }
  for (int i = 1; i <= n; i++) {
      cnt[i][0] += cnt[i - 1][0];
      cnt[i][1] += cnt[i - 1][1];
      if (cnt[i][0]) chkmax(b[i], a[i] + t);
      if (cnt[i][1]) chkmax(b[i], a[i + 1] + t);
      chkmax(b[i], b[i - 1] + 1);
  }
  for (int i = 1; i <= n; i++) {
      if (x[i] == n) continue;
      if (b[x[i]] >= a[x[i] + 1] + t) {
          printf("No\n");
          return 0;
      }
  }
  printf("Yes\n");
  for (int i = 1; i <= n; i++)
      write(b[i]), putchar(' ');
  printf("\n");
  return 0;
}

**【B】**Subway Pursuit

【思路要点】

  • 我们可以通过朴素的二分在 O ( L o g N ) 的操作次数内将目标确定在一个长度为 4 k + 1 的区间内。
  • 注意到操作次数很多,我们不妨随机选取该区间内的一个位置询问,若找到目标,则问题解决,否则,下一秒目标可能出现的位置扩大为一个长度为 6 k + 1 的区间,我们仍然可以通过朴素的二分将其确定在一个长度为 4 k + 1 的区间内并重复随机猜测。
  • 由一个长度为 6 k + 1 的区间通过二分确定一个长度为 4 k + 1 的区间之多需要使用 5 次操作,因此我们每 6 次操作就能进行一次正确率不低于 1 41 猜测,在次数限制内能够进行的操作数约为 4400 6 733 ,每一次都未猜对的概率约为 ( 40 41 ) 733 1.3 10 8
  • 时间复杂度 O ( 4500 ) ,在规定的操作次数内完成任务的概率约为 99.999998 % ,应确保使用的随机方式没有被卡足够随机。

【代码】


#include<bits/stdc++.h>

using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
  for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
ll n; int k;
unsigned seed;
unsigned Rand() {
  seed = seed * seed + rand();
  return seed;
}
int main() {
  read(n), read(k);
  ll l = 1, r = n;
  while (true) {
      if (r - l + 1 <= 4 * k + 5) {
          int len = r - l + 1;
          int tmp = Rand() % len;
          cout << l + tmp << ' ' << l + tmp << endl;
          string res; cin >> res;
          if (res == "Yes") return 0;
          l = max(l - k, 1ll);
          r = min(r + k, n);
      } else {
          ll mid = (l + r) / 2;
          cout << l << ' ' << mid << endl;
          string res; cin >> res;
          if (res == "Yes") {
              l = max(l - k, 1ll);
              r = min(mid + k, n);
          } else {
              l = max(mid + 1 - k, 1ll);
              r = min(r + k, n);
          }
      }
  }
  return 0;
}

**【C】**Network Safety

【思路要点】

  • 对于一条边,它会造成故障当且仅当它一侧的节点被入侵,另一侧的没有,并且入侵的病毒编号为其两侧的点权异或和。
  • 因此,对于一种病毒 x ,两侧的点权异或和为 x 的边连通的每一个连通分量要么都被入侵,要么都不被入侵,即记连通分量数为 i x 对答案的贡献为 2 i
  • 用并查集处理存在两侧的点权异或和为 x 的边的 x ,其余的 x 贡献均为 2 N
  • 时间复杂度 O ( M L o g M + M α ( N ) )

【代码】


#include<bits/stdc++.h>

using namespace std;
const int MAXN = 5e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
  for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
struct info {int l, r; ll delta; } a[MAXN];
int n, m, k, bit[MAXN], f[MAXN];
bool vis[MAXN]; ll x[MAXN];
bool cmp(info a, info b) {
  return a.delta < b.delta;
}
int F(int x) {
  if (f[x] == x) return x;
  else return f[x] = F(f[x]);
}
int main() {
  read(n), read(m), read(k);
  for (int i = 1; i <= n; i++)
      read(x[i]);
  bit[0] = 1;
  for (int i = 1; i <= n; i++)
      bit[i] = bit[i - 1] * 2 % P;
  for (int i = 1; i <= m; i++) {
      read(a[i].l), read(a[i].r);
      a[i].delta = x[a[i].l] ^ x[a[i].r];
  }
  sort(a + 1, a + m + 1, cmp);
  int cnt = 0, ans = 0;
  for (int i = 1, nxt; i <= m; i = nxt + 1) {
      for (nxt = i; nxt < m && a[nxt + 1].delta == a[i].delta; nxt++);
      int comp = n; cnt++;
      for (int j = i; j <= nxt; j++) {
          f[a[j].l] = a[j].l;
          f[a[j].r] = a[j].r;
          comp -= !vis[a[j].l];
          vis[a[j].l] = true;
          comp -= !vis[a[j].r];
          vis[a[j].r] = true;
      }
      for (int j = i; j <= nxt; j++)
          f[F(a[j].l)] = F(a[j].r);
      for (int j = i; j <= nxt; j++) {
          if (vis[a[j].l]) comp += F(a[j].l) == a[j].l;
          vis[a[j].l] = false;
          if (vis[a[j].r]) comp += F(a[j].r) == a[j].r;
          vis[a[j].r] = false;
      }
      ans = (ans + bit[comp]) % P;
  }
  ans = (ans + ((1ll << k) - cnt) % P * bit[n]) % P;
  writeln(ans);
  return 0;
}

**【D】**You Are Given a Tree

【思路要点】

  • 有一种朴素的贪心,可以对于一个任意的 k ,在 O ( N ) 的时间内求出答案,即从叶子向根贪心,若能够在某个点的子树内形成一条路径,则令其形成,否则返回子树内能够延伸的最长长度。
  • 我们发现对于一个任意的 k A n s k N k 并且 A n s k A n s k + 1 ,因此若我们设置一个阈值 α ,当 k α 时直接用上述贪心计算答案,否则利用 A n s k 的单调性二分出每一种 A n s k 取值的 k 的范围,时间复杂度将为 O ( N α + N 2 L o g N α )
  • α = O ( N L o g N ) ,时间复杂度 O ( N N L o g N )

【代码】


#include<bits/stdc++.h>

using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
  for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
vector <int> a[MAXN];
int n, tot, father[MAXN], p[MAXN], ans[MAXN];
void work(int pos, int fa) {
  father[pos] = fa;
  for (unsigned i = 0; i < a[pos].size(); i++)
      if (a[pos][i] != fa) work(a[pos][i], pos);
  p[++tot] = pos;
}
int f(int x) {
  if (ans[x] != -1) return ans[x];
  static int res[MAXN]; int tans = 0;
  for (int i = 1; i <= n; i++) {
      int Max = 1, Nax = 1, pos = p[i];
      for (unsigned i = 0; i < a[pos].size(); i++)
          if (a[pos][i] != father[pos]) {
              int tmp = res[a[pos][i]] + 1;
              if (tmp > Max) {
                  Nax = Max;
                  Max = tmp;
              } else chkmax(Nax, tmp);
          }
      if (Max + Nax - 1 >= x) tans++, res[pos] = 0;
      else res[pos] = Max;
  }
  return ans[x] = tans;
}
int main() {
  read(n);
  for (int i = 1; i <= n - 1; i++) {
      int x, y; read(x), read(y);
      a[x].push_back(y);
      a[y].push_back(x);
  }
  work(1, 0);
  memset(ans, -1, sizeof(ans));
  for (int i = 1, nxt; i <= n; i = nxt + 1) {
      int now = f(i);
      if (now >= 65 || f(i + 1) != now) {
          ans[nxt = i] = now;
          continue;
      }
      int l = i + 1, r = n;
      while (l < r) {
          int mid = (l + r + 1) / 2;
          if (f(mid) == now) l = mid;
          else r = mid - 1;
      }
      nxt = l;
      for (int j = i; j <= nxt; j++)
          ans[j] = now;
  }
  for (int i = 1; i <= n; i++)
      writeln(ans[i]);
  return 0;
}

**【E】**Summer Oenothera Exhibition

【思路要点】

  • 原题本质是对于每个询问 x i ,将序列分为最少的若干段,使得每一段的极差不超过 w x i ,求最少分的段数 1
  • 显然一个朴素的“尽量分在前面一段”贪心可以对于每个询问,在 O ( N ) 的时间内给出答案,并且另一个简单的事实是我们可以通过 S T 表在 O ( L o g N ) 的时间内确定一段的结尾位置。
  • 考虑进行平衡规划,我们设定一个阈值 α ,维护每一个点 i 向后最长能够保证满足极差限制的位置 n x t i (仅维护到 i + α ),以及每一个点 i 通过 n x t i 在不走过 i + α 的情况下向后最长走的段数 c n t i 和走到的位置 j u m p i 。以上信息能够在 O ( N α 2 ) 的时间内进行维护。
  • 对于 n x t i i α i ,我们用 j u m p i 向后模拟,次数不会超过 O ( N α ) ;对于 n x t i i > α i ,我们通过 S T 表在 O ( L o g N ) 的时间内确定这一段的结尾位置,次数同样不会超过 O ( N α ) ,至此,我们得到了一个时间复杂度为 O ( N α 2 + N 2 L o g N α ) 的做法,我们发现二分部分的时间复杂度过高,导致总体时间复杂度无法通过设置 α 显著降低。
  • 再次进行平衡规划,设定一个阈值 β > α ,对 n x t i 的维护由仅维护到 i + α 改为维护到 i + β ,则维护信息的时间复杂度变为 O ( N α 2 + N β )
  • 对于 n x t i i α i ,我们用 j u m p i 向后模拟,次数不会超过 O ( N α ) ;对于 α < n x t i i β i ,我们用 n x t i 向后模拟,次数不会超过 O ( N α ) ;对于 n x t i i > β i ,我们通过 S T 表在 O ( L o g N ) 的时间内确定这一段的结尾位置,次数不会超过 O ( N β ) 。至此,我们得到了一个时间复杂度为 O ( N α 2 + N β + N 2 α + N 2 L o g N β ) 的做法。
  • α = O ( N 1 3 ) , β = O ( N 2 3 ) ,时间复杂度 O ( N 5 3 + N 4 3 L o g N )

【代码】


#include<bits/stdc++.h>

using namespace std;
const int MAXN = 2e5 + 5;
const int MAXLOG = 20;
const int INF = 1e9 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
  x = 0; int f = 1;
  char c = getchar();
  for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
  for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
  x *= f;
}
template <typename T> void write(T x) {
  if (x < 0) x = -x, putchar('-');
  if (x > 9) write(x / 10);
  putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
  write(x);
  puts("");
}
struct info {int x, home; } b[MAXN];
bool operator < (info a, info b) {return a.x > b.x; }
int n, w, q, k1, k2, a[MAXN];
int cnt[MAXN], jump[MAXN], ans[MAXN];
int nxt[MAXN], nowMax[MAXN], nowMin[MAXN];
int Min[MAXN][MAXLOG], Max[MAXN][MAXLOG];
priority_queue <info> Heap;
bool cmp(info a, info b) {
  return a.x < b.x;
}
void update(int pos, int x) {
  while (nxt[pos] <= n && nxt[pos] - pos <= k2 && nowMax[pos] - nowMin[pos] <= x) {
      nxt[pos]++;
      chkmax(nowMax[pos], a[nxt[pos]]);
      chkmin(nowMin[pos], a[nxt[pos]]);
  }
  if (nxt[pos] <= n && nxt[pos] - pos <= k1) Heap.push((info) {nowMax[pos] - nowMin[pos], pos});
  static int q[MAXN]; int top = 0;
  jump[pos] = pos, cnt[pos] = 0, q[++top] = pos;
  while (jump[pos] != n + 1 && nxt[jump[pos]] - pos <= k1) {
      jump[pos] = nxt[jump[pos]];
      cnt[pos]++, q[++top] = jump[pos];
  }
  static int tmp[MAXN]; tmp[pos] = top;
  static bool updated[MAXN]; updated[pos] = true;
  for (int i = pos - 1; i >= 1 && i >= pos - k1; i--) {
      if (!updated[nxt[i]]) continue;
      while (q[top] - i > k1) top--;
      jump[i] = q[top], cnt[i] = cnt[nxt[i]] + 1 - tmp[nxt[i]] + top;
      tmp[i] = top, updated[i] = true;
  }
  for (int i = pos; i >= 1 && i >= pos - k1; i--) updated[i] = false;
}
int query(int x) {
  static int tmp[MAXN]; int tot = 0;
  while (!Heap.empty() && Heap.top().x <= x) {
      tmp[++tot] = Heap.top().home;
      Heap.pop();
  }
  sort(tmp + 1, tmp + tot + 1);
  for (int i = 1; i <= tot; i++)
      update(tmp[i], x);
  int ans = 0, pos = 1;
  while (pos != n + 1) {
      int dist = nxt[pos] - pos;
      if (dist <= k1) {
          ans += cnt[pos];
          pos = jump[pos];
      } else {
          while (nxt[pos] <= n && nxt[pos] - pos <= k2 && nowMax[pos] - nowMin[pos] <= x) {
              nxt[pos]++;
              chkmax(nowMax[pos], a[nxt[pos]]);
              chkmin(nowMin[pos], a[nxt[pos]]);
          }
          dist = nxt[pos] - pos;
          if (dist <= k2) {
              ans += 1;
              pos = nxt[pos];
          } else {
              ans += 1;
              int nMax = a[pos], nMin = a[pos];
              for (int i = MAXLOG - 1; i >= 0; i--) {
                  if (pos + (1 << i) - 1 <= n) {
                      int tMax = max(nMax, Max[pos][i]);
                      int tMin = min(nMin, Min[pos][i]);
                      if (tMax - tMin <= x) {
                          pos += 1 << i;
                          nMax = tMax;
                          nMin = tMin;
                      }
                  }
              }
          }
      }
  }
  return ans;
}
void init() {
  a[n + 1] = -INF;
  for (int p = 1; p < MAXLOG; p++)
  for (int i = 1, j = (1 << (p - 1)) + 1; j <= n; i++, j++) {
      Max[i][p] = max(Max[i][p - 1], Max[j][p - 1]);
      Min[i][p] = min(Min[i][p - 1], Min[j][p - 1]);
  }
  k1 = pow(n, 1.0 / 3.0), k2 = pow(n, 2.0 / 3.0);
  for (int i = 1; i <= n; i++) {
      nxt[i] = i + 1;
      nowMin[i] = min(a[i], a[i + 1]);
      nowMax[i] = max(a[i], a[i + 1]);
      Heap.push((info) {nowMax[i] - nowMin[i], i});
      jump[i] = min(i + k1, n + 1);
      cnt[i] = jump[i] - i;
  }
}
int main() {
  read(n), read(w), read(q);
  for (int i = 1; i <= n; i++) {
      read(a[i]);
      Max[i][0] = Min[i][0] = a[i];
  }
  init();
  for (int i = 1; i <= q; i++) {
      int x; read(x);
      b[i] = (info) {w - x, i};
  }
  sort(b + 1, b + q + 1, cmp);
  for (int i = 1; i <= q; i++)
      ans[b[i].home] = query(b[i].x);
  for (int i = 1; i <= q; i++)
      writeln(ans[i] - 1);
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_39972971/article/details/82595840
今日推荐