看了官方题解视频,觉得有*点简略,和评论区的感觉一样(
这里重新写一篇连铜牌都能看懂的 题解。
题意
给定 n 个区间 [ai,bi),要求将它们分成 k 组,最大化每组交的长度之和,并且每组的交长度不能为0。1≤k≤n≤5000 , 1≤a<b<105 。
思路
对于包含其他区间的大区间,我们考虑两种情况对其进行分配 1.归属到一个被它包含的区间所在的组,不影响答案 2.独自一组,长度直接算入答案 很显然,要么为第一种情况,要么为第二种情况,因此可以单独处理。 剩下的就是互不从属的小区间,我们令
d
p
i
,
j
dp_{i,j}
dpi,j?代表前j个数分成i组的交长度和,
(
a
i
,
b
i
)
(a_i,b_i)
(ai?,bi?)代表剩下的小区间。 而每次转移只考虑最后一个多出来的组从哪(下标为k)开始 也就是可以写为
d
p
[
i
]
[
j
]
=
d
p
[
i
?
1
]
[
k
]
+
b
k
+
1
?
a
j
dp[i][j] = dp[i-1][k]+b_{k+1}-a_j
dp[i][j]=dp[i?1][k]+bk+1??aj? 其图示如下 显然,按照一般的枚举方式,我们需要分别枚举i,j,k三个维度。 此时的复杂度将会高达
O
(
n
3
)
O(n^3)
O(n3) 但我们观察发现: 原转移方程可以改写为
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
?
1
]
[
k
]
+
b
k
+
1
)
?
a
j
,
?
w
h
e
n
?
b
k
+
1
>
a
j
dp[i][j] = max(dp[i-1][k]+b_{k+1})-a_j, \ when \ b_{k+1} >a_j
dp[i][j]=max(dp[i?1][k]+bk+1?)?aj?,?when?bk+1?>aj? 显然
m
a
x
(
d
p
[
i
?
1
]
[
k
]
+
b
k
+
1
)
max(dp[i-1][k]+b_{k+1})
max(dp[i?1][k]+bk+1?)是可以使用单调队列进行维护最大值的。 不会维护的以及不知道为什么能优化的,请看这个博客中的例子: https://www.cnblogs.com/ljy-endl/p/11638389.html 最后,我们再综合一下之前预处理的那些大区间,就得到了最后的答案。 代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3+5;
struct node{
int l,r;
}a[N*2],small[N*2];
int bL[N],q[N],dp[N][N];
int main()
{
memset(dp,-1,sizeof dp);
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i].l>>a[i].r;
sort(a+1,a+n+1,[=](node x,node y){
return x.l!=y.l?x.l<y.l:x.r>y.r;
});
int maxn = 0x3f3f3f3f;
int cnt=0,tot=0;
for(int i=n;i>0;i--)
{
if(a[i].r>=maxn)bL[++cnt] = a[i].r-a[i].l;
else maxn = a[i].r,small[++tot]=a[i];
}
reverse(small+1,small+tot+1);
sort(bL+1,bL+cnt+1,greater<int>());
dp[0][0]=0;
for(int i=1;i<=k;i++)
{
int h=1,t=0;
for(int j=1;j<=tot;j++)
{
if(dp[i-1][j-1]>-1){
while (h<=t&&
dp[i-1][q[t]]+small[q[t]+1].r<=dp[i-1][j-1]+small[j].r)
t--;
q[++t]=j-1;
}
while (h<=t&&small[q[h]+1].r<=small[j].l)h++;
if(h<=t)dp[i][j]=dp[i-1][q[h]]+small[q[h]+1].r-small[j].l;
}
}
int tmp=0,ans=0;
for(int i=0;i<=min(k,n);i++)
{
tmp+=bL[i];
if(dp[k-i][tot]>-1){
ans = max(ans,dp[k-i][tot]+tmp);
}
}
cout<<ans<<endl;
}
|