Codeforces1890 E2. Doremy‘s Drying Plan (Hard Version) (线段树优化dp)

题面:

https://codeforces.com/contest/1890/problem/E2
在这里插入图片描述

解法:

dp[i][j]表示前i个格子, 使用了j次魔法, 且格子i已经没有雨的最大答案
转移方程:
dp[i][j]=max{dp[t][j-d_t]}+0
其中t属于[-1,i-1], d_t表示覆盖了格子i的雨中,满足l在[t+1,i]范围内的雨的数量.

为什么不需要考虑覆盖了i,但是l<t-2的雨呢?
因为我们对dp[t]的定义是格子t已经没有雨了,
即在计算dp[t]的时,l<t-2的已经被消掉了,所以不需要考虑.

但转移方程需要枚举j和t, 因此复杂度是O(n^1*k)的.
需要想办法优化.

我们对覆盖了格子i的雨按左端点排序,
根据[-1,i]中每个格子被这些雨覆盖的次数,
可以发现在考虑到在一定范围内d_t是固定的, 如下图:

在这里插入图片描述

我们可以枚举d_t,(或者枚举雨的左端点)
然后根据d_t计算出t的范围(用这些雨的左端点可以计算出来).
问题就变成计算范围内dp数组的最大值了,可以用线段树维护最大值.

具体实现方法是建立k+0棵线段树,第k棵树表示dp[][j]
Code:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PI pair<int, int>
const int maxm = 2e5 + 5;
struct ST {
  int a[maxm << 2];
  int ma[maxm << 2];
  inline void pushup(int node) {
    ma[node] = max(ma[node * 2], ma[node * 2 + 1]);
  }
  void update(int x, int val, int l, int r, int node) {
    if (l == r) {
      ma[node] = a[node] = val;
      return;
    }
    int mid = (l + r) / 2;
    if (x <= mid) {
      update(x, val, l, mid, node * 2);
    } else {
      update(x, val, mid + 1, r, node * 2 + 1);
    }
    pushup(node);
  }
  void build(int l, int r, int node) {
    if (l == r) {
      a[node] = ma[node] = -2e9;
      if (l == 0) {
        a[node] = ma[node] = 0;
      }
      return;
    }
    int mid = (l + r) / 2;
    build(l, mid, node * 2);
    build(mid + 1, r, node * 2 + 1);
    pushup(node);
  }
  int ask(int st, int ed, int l, int r, int node) {
    if (st <= l && ed >= r) {
      return ma[node];
    }
    int mid = (l + r) / 2;
    int ans = -2e9;
    if (st <= mid) {
      ans = max(ans, ask(st, ed, l, mid, node * 2));
    }
    if (ed > mid) {
      ans = max(ans, ask(st, ed, mid + 1, r, node * 2 + 1));
    }
    return ans;
  }
} T[11];
vector<int> add[maxm];
vector<int> del[maxm];
int l[maxm], r[maxm];
int a[maxm];
int n, m, k;
void solve() {
  cin >> n >> m >> k;
  // reset
  for (int i = 1; i <= n; i++) {
    add[i].clear();
    del[i].clear();
  }
  for (int j = 0; j <= k; j++) {
    T[j].build(0, n, 1);
  }
  //
  for (int i = 1; i <= m; i++) {
    cin >> l[i] >> r[i];
    add[l[i]].push_back(i);
    del[r[i] + 1].push_back(i);
  }
  set<int> s;
  for (int i = 1; i <= n; i++) {
    for (int p : add[i]) {
      s.insert(p);
    }
    for (int p : del[i]) {
      s.erase(p);
    }
    if (s.size() <= k) {
      vector<int> l_pos;
      l_pos.push_back(0);
      for (const int p : s) {
        l_pos.push_back(l[p]);
      }
      sort(l_pos.begin(), l_pos.end());
      // dp[i][j]=max{dp[t][j-d_t]}+1
      // d_t表示[t+1,i]中有多少个线段覆盖了i
      // 且这些线段满足左端点l>t
      for (int j = s.size(); j <= k; j++) {
        int dp_ij = 1;
        for (int idx = 0; idx < l_pos.size(); idx++) {
          int d_t = s.size() - idx;
          int t_l = l_pos[idx];
          int t_r = (idx == (int)l_pos.size() - 1 ? i - 1 : l_pos[idx + 1] - 1);
          // t_l > t_r当且仅当l_pos[idx] == l_pos[idx+1]
          if (t_l > t_r) continue;
          if (j - d_t < 0) continue;
          // 对于[t_l,t_r]范围, d_t的值都相同
          int dp_t = T[j - d_t].ask(t_l, t_r, 0, n, 1);
          dp_ij = max(dp_ij, dp_t + 1);
        }
        T[j].update(i, dp_ij, 0, n, 1);
      }
    }
  }
  int ans = 0;
  for (int j = 0; j <= k; j++) {
    ans = max(ans, T[j].ask(0, n, 0, n, 1));
  }
  cout << ans << endl;
}
signed main() {
#define MULTI_CASE
  ios::sync_with_stdio(0);
  cin.tie(0);
#ifndef ONLINE_JUDGE
  freopen("../in.txt", "r", stdin);
  freopen("../out.txt", "w", stdout);
#endif
#ifdef MULTI_CASE
  int T;
  cin >> T;
  while (T--)
#endif
    solve();
  return 0;
}
官方题解:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值