Codeforces Round 887 (Div. 1)

发布时间 2023-12-29 20:44:50作者: DCH233

Codeforces Round 887 (Div. 1)

A

先来个二分。注意到这样一件事:考虑是 \(a_i\) 失效的最小时间 \(t_i\),发现 \(t\) 有单调性。所以从大到小考虑 \(a\),每次相当于将二分的值减去一个值,复杂度 \(O(\sum n(\log n + \log k))\)

code
const int N = 2e5 + 10;
int n; ll k;
ll a[N];

ll calc(ll x) {
  ll res = 0;
  for(int i = n; i >= 1; --i) {
    ll d;
    if(x < a[i]) d = 0;
    else d = min(k, (x - a[i]) / i + 1);
    x -= d; res += d;
  }
  return res;
}

void solve() {
  cin >> n >> k;
  for(int i = 1; i <= n; ++i)
    cin >> a[i];
  ll ans = 0;
  for(ll l = 0, r = 2e14; l <= r; ) {
    ll mid = (l + r) / 2;
    if(calc(mid) >= mid) ans = mid + 1, l = mid + 1;
    else r = mid - 1;
  }
  cout << ans << '\n';
}

B

不难发现所求序列的绝对值是一个 \(1\)\(n\) 的排列。画个图可以发现,如果我们按照 \(b\) 的绝对值从大到小确定这个排列,那么限制是好描述的。设 \(i\)\(b\) 中系数为 \(t_i \in \{1, -1\}\)。那么有 \(\sum_{i=1}^{j-1}t_i + jt_j = a\)。这样,我们发现若存在合法解,则解唯一。所以可以直接逐位确定。

code
void solve() {
  cin >> n;
  for(int i = 1; i <= n; ++i)
    cin >> a[i].first, a[i].second = i;
  sort(a + 1, a + n + 1);
  for(int i = n, l = 1, r = n, pre = 0; i >= 1; --i) {
    if(a[r].first > i) {
      cout << "NO\n";
      return ;
    } else if(a[l].first == 0 && a[r].first == i) {
      cout << "NO\n";
      return ;
    } else if(a[r].first == i) {
      ++pre, ans[a[r].second] = i;
      --a[l].first;
      if(l < --r) a[r].first -= pre;
    } else if(a[l].first == 0) {
      ans[a[l].second] = -i;
      if(++l < r) a[l].first -= pre;
    } else {
      cout << "NO\n";
      return ;
    }
  }
  cout << "YES\n";
  for(int i = 1; i <= n; ++i)
    cout << ans[i] << " ";
  cout << '\n';
}

C

题意转化为初始 \(c_i = 0\),每次对 \(c\) 区间 \(+1\),最后使得 \(c_i \equiv a_i \pmod k\)。不难发现若最终 \(c_i\) 确定,则答案为 \(\sum \max(0, c_i - c_{i - 1})\)

\(d_i = c_i - c_{i - 1}\),考虑我们可以干啥,发现我们是答案变优的手段是将若干个 \(d_i\) 增大 \(k\),这样就会消去某些位置的贡献。发现最终 \(|d_i| < k\),否则不调整一定不劣。考虑限制,发现类似括号序,每个前缀中 \(+k\) 的数量大于等于消去贡献位置的数量。发现这玩意不好 dp,于是考虑贪心。不难得到费用流模型:对 \(d_i < 0\) 连边 \((S, i, 1, d_i + k)\),对 \(d_i > 0\) 连边 \((i, T, 1, -d_i)\),任意 \(i\) 连边 \((i, i + 1, 1, 0)\)。直接使用小根堆模拟即可。

code
void solve() {
  cin >> n >> k;
  for(int i = 1; i <= n; ++i) {
    cin >> a[i];
    if(a[i] == k) a[i] = 0;
  }
  priority_queue<int, vector<int>, greater<int>> q;
  ll sum = 0;
  for(int i = 1; i <= n; ++i) {
    int d = a[i] - a[i - 1];
    sum += max(d, 0);
    if(d < 0) {
      q.push(k + d);
    } else if(q.size()) {
      int x = q.top(); 
      if(x < d) {
        sum += x - d;
        q.pop(), q.push(d);
      }
    }
  }
  cout << sum << '\n';
}

D

暴力 dp,打表发现可以有性质,直接做就行了。

code
const int N = 2e5 + 10;
int n, k;
string s;

struct info {
  int l, r, p;
  info operator + (const int &x) const {
    return {l + x, r + x, p == -1 ? -1 : p + x};
  }
  info operator + (const info &x) const {
    if(r + 2 == x.l) return {l, x.r, r + 1};
    if(x.r + 2 == l) return {x.l, r, x.r + 1};
    if(~p && p < x.l) return {l, max(r, x.r), p};
    if(~p && p > x.r) return {min(l, x.l), r, p};
    if(~x.p && x.p < l) return {x.l, max(r, x.r), x.p};
    if(~x.p && x.p > r) return {min(l, x.l), x.r, x.p};
    return {min(l, x.l), max(r, x.r), p == x.p ? p : -1};
  }
  bool check(int x) {
    return l <= x && x <= r && x != p;
  }
}f[N][2];

string ans;
void out(int n, int c) {
  k -= (s[n] - 'A' != c);
  for(int nc = 0; nc < 2; ++nc) {
    if(f[n - 1][nc].check(k - (c != nc))) {
      k -= (c != nc);
      out(n - 1, nc);
      break;
    }
  }
  ans += c + 'A';
}

void solve() {
  cin >> n >> k >> s;
  f[0][0] = {s[0] == 'B', s[0] == 'B', -1};
  f[0][1] = {s[0] == 'A', s[0] == 'A', -1};
  for(int i = 1; i < n; ++i) {
    k -= (s[i] != s[i -  1]);
    if(s[i] == 'A') {
      f[i][0] = (f[i - 1][0] + (f[i - 1][1] + 1));
      f[i][1] = ((f[i - 1][0] + 1) + f[i - 1][1]) + 1;
    } else {
      f[i][0] = (f[i - 1][0] + (f[i - 1][1] + 1)) + 1;
      f[i][1] = ((f[i - 1][0] + 1) + f[i - 1][1]);
    }
  }
  ans.clear();
  if(f[n - 1][0].check(k)) {
    cout << "YES\n";
    out(n - 1, 0);
    cout << ans << '\n';
    return ;
  } else if(f[n - 1][1].check(k)) {
    cout << "YES\n";
    out(n - 1, 1);
    cout << ans << '\n';
  } else {
    cout << "NO\n";
  }
}

E

不难发现,如果出现凸这种形状,那么大区间是没用的。这样可以先求出所有实际上有用的区间,那么这些区间的左右端点是不能动的,其他部分可以乱搞。

感觉一下,认为最多出现一种原序列没有的数,证明很简单,如果有两种那么把小的变成大的依然满足限制。我们考虑怎么安排新的这种数,发现需要让新区间包含一个值比他大的小区间。

考虑如果所有区间都已确定,那么接下来每个位置只需要填包含这个区间的最大的数即可,先不管新的数求一边。现在剩下的问题就是如何确定新的数的区间。我们考虑枚举包含的那个比他大的区间,不难发现会如果某一侧有比新数小的数那么会直接选了,如果没有则会选最小的。可以用依托数据结构和 vector 上二分维护做到 \(O(n \log n)\)

写一个下午发现做法一直假,麻了。在细节上卡了整整三天才过。细节是什么呢?如果我们去掉某些区间后,一些位置直接贪得不到一个确定的数,那么我们必然需要新数,否则可以不使用新数。

code
const int N = 1e6 + 10;
int n, m, o[N], b[N];

struct info {
  int l, r, x;
}a[N];
map<int, int> fl, fr;

struct BIT {
  int tr[N];
  int lowbit(int x) {
    return x & -x;
  }
  void clear() {
    for(int i = 1; i <= n; ++i)
      tr[i] = 0;
  }
  void add(int x, int v) {
    for(; x <= n; x += lowbit(x))
      tr[x] = max(tr[x], v);
  }
  int query(int x) {
    int res = 0;
    for(; x; x -= lowbit(x)) 
      res = max(res, tr[x]);
    return res;
  }
}s;

int occ[N];
vector<int> vec;

void clear() {
  fl.clear(), fr.clear();
  for(int i = 1; i <= n; ++i)
    b[i] = occ[i] = 0;
  vec.clear(), s.clear();
}

void solve() {
  clear();
  cin >> n;
  for(int i = 1; i <= n; ++i) {
    cin >> o[i];
    if(!fl.count(o[i])) fl[o[i]] = i;
    fr[o[i]] = i;
  }
  m = 0;
  for(auto [x, l] : fl) {
    int r = fr[x];
    a[++m] = {l, r, x};
  }
  sort(a + 1, a + m + 1, [](info x, info y) {
    return x.l == y.l ? x.r < y.r : x.l > y.l;
  });
  vector<int> vv;
  int tmp = m; m = 0;
  for(int i = 1; i <= tmp; ++i) {
    int l = a[i].l, r = a[i].r, x = a[i].x;
    int p = s.query(r);
    if(p < x) {
      a[++m] = a[i];
      s.add(r, x);
      occ[l] = occ[r] = m;
      vv.eb(x);
    } 
  }
  sort(vv.begin(), vv.end());
  int lst = 0;
  for(int x : vv) {
    if(lst != x - 1) vec.eb(x - 1);
    lst = x;
  }
  set<int, greater<int>> se;
  se.insert(0);
  vector<pii> vb;
  vector<int> vmn, vmx;
  for(int i = 1; i <= n; ++i) {
    if(occ[i]) {
      int x = a[occ[i]].x; b[i] = x;
      if(se.find(x) != se.end()) se.erase(x);
      else if(a[occ[i]].r > a[occ[i]].l) se.insert(x);
    } else {
      b[i] = *se.begin();
      vb.emplace_back(mp(b[i], i));
    }
  }
  sort(vb.begin(), vb.end());
  vmn.resize(vb.size()), vmx.resize(vb.size());
  for(int i = 0, mn = n + 1, mx = 0; i < sz(vb); ++i)
    vmn[i] = -(mn = min(mn, vb[i].second)), vmx[i] = mx = max(mx, vb[i].second);
  auto qmn = [&](int x) {
    int rk = lower_bound(vmn.begin(), vmn.end(), -x) - vmn.begin();
    if(rk == sz(vb)) return -1;
    return vb[rk].second;
  };
  auto qmx = [&](int x) {
    int rk = lower_bound(vmx.begin(), vmx.end(), x) - vmx.begin();
    if(rk == sz(vb)) return -1;
    return vb[rk].second;
  };
  vec.emplace_back(-1);
  sort(vec.begin(), vec.end());
  auto get = [&](int x) {
    return *(lower_bound(vec.begin(), vec.end(), x + 1) - 1);
  };
  ll ms = 0;
  for(int i = 1; i <= n; ++i)
    if(b[i] == 0) ms = -1e18;
  int L = -1, R = -1, w = -1;
  ll sum = 0; int mn = n + 1, mx = 0;
  sort(a + 1, a + m + 1, [](info x, info y) {
    return x.x < y.x;
  });
  for(int i = 1, p = 0; i <= m; ++i) {
    int x = a[i].x, v = get(x);
    if(v <= 0) continue;
    while(p < sz(vb) && vb[p].first <= v) {
      mn = min(mn, vb[p].second), mx = max(mx, vb[p].second);
      sum += vb[p++].first;
    }
    ll t = 1ll * p * v - sum;
    int lp = 0, rp = 0;
    if(mn > a[i].l) {
      int ql = qmn(a[i].l);
      if(ql == -1) continue;
      assert(b[ql] > v);
      t = t - b[ql] + v, lp = ql;
    } 
    if(mx < a[i].r) {
      int qr = qmx(a[i].r);
      if(qr == -1) continue;
      assert(b[qr] > v);
      t = t - b[qr] + v, rp = qr;
    }
    if(t > ms) {
      ms = t, w = v;
      L = lp, R = rp;
    }
  }
  for(int i = 1; i <= n; ++i)
    if(occ[i]) cout << b[i] << ' ';
    else {
      if(i == L || i == R) cout << w << ' ';
      else cout << max(w, b[i]) << ' ';
    }
  cout << '\n';
}

int main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  int T; cin >> T;
  while(T--) solve();
}