2021牛客暑期多校训练营2 G. League of Legends

链接

https://ac.nowcoder.com/acm/contest/11253/G

题意

将 $n$ 个区间 $[l_i,r_i)$ 分配到 $k$ 组,使每组交集的和最大,每组都不能为空。

思路

考虑按顺序依次分配区间,每次分配可以分为三种情况:

  1. 某组为空;
  2. 某组交集包含于当前区间;
  3. 某组交集不包含于当前区间。

放入第一种情况,总贡献增加;

放入第二种情况,总贡献不变;

放入第三种情况,总贡献减小。

只可能放入前两种情况的组内,因此可以在一开始将包含小区间的大区间挑出来。

将区间按左端点从小到大,右端点从大到小排序,单调栈处理出所有大区间。

这些大区间放入第二种情况因为交集不变所以不需要考虑,只需要考虑单独放入空的组内。

对于剩下的小区间,将左端点从小到大考虑 DP,记 $dp_{i,j}$ 为前 $j$ 个区间分为 $i$ 组的最大贡献。

在分配时只可能将连续的几个区间放入同个组内,因为这样才能使交集尽可能大。

所以 $dp_{i,j}=\max{dp_{i-1,t-1}+r_t-l_j}=\max{dp_{i-1,t-1}+r_t}-l_j$ $(r_t>l_j)$

可以用单调队列优化,时间复杂度为 $O(nk)$。

当 $i>j$ 时,并不存在分配方案,因此将数组初始化为一个极小值,而 $dp_{0,0}=0$。

如果存在 $k-i$ 个大区间,那么可以更新答案。

将大区间按长度从大到小排序,如果分配时剩下空的组用大区间去填充,每个组依次选较大的一个区间填充,剩下的大区间放入其子集所在组。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <bits/stdc++.h>
#define SZ(x) (int)(x).size()
#define ALL(x) (x).begin(),(x).end()
#define PB push_back
#define EB emplace_back
#define MP make_pair
#define FI first
#define SE second
using namespace std;
typedef double DB;
typedef long double LD;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int,int> PII;
typedef vector<int> VI;
typedef vector<PII> VPII;
// head
const int N=5005;
int dp[N][N],sum[N];
PII a[N];
VPII b;
VI c;
bool cmp(PII a,PII b) {
return a.FI<b.FI||a.FI==b.FI&&a.SE>b.SE;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,k;cin>>n>>k;
for(int i=0;i<n;i++) cin>>a[i].FI>>a[i].SE;
sort(a,a+n);
for(int i=0;i<n;i++) {
while(SZ(b)&&a[i].SE<=b.back().SE) {
c.PB(b.back().SE-b.back().FI);
b.pop_back();
}
b.PB(a[i]);
}
sort(ALL(c),greater<int>());
for(int i=0;i<SZ(c);i++) sum[i+1]=sum[i]+c[i];
memset(dp,0xc0,sizeof dp);
dp[0][0]=0;
int res=0;
for(int i=1;i<=k;i++) {
deque<int> q;
for(int j=0;j<SZ(b);j++) {
while(SZ(q)&&dp[i-1][q.back()]+b[q.back()].SE<=dp[i-1][j]+b[j].SE) q.pop_back();
while(SZ(q)&&b[q[0]].SE<=b[j].FI) q.pop_front();
q.PB(j);
dp[i][j+1]=dp[i-1][q[0]]+b[q[0]].SE-b[j].FI;
}
if(SZ(c)>=k-i) res=max(res,dp[i][SZ(b)]+sum[k-i]);
}
cout<<res<<'\n';
return 0;
}