题面:
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;
}