题目
思路
在很多年以前,有一道叫做守卫,是一道区间 d p \tt dp dp 的题,而我惨痛爆零。它教给了我很重要的一课, d p \tt dp dp 是需要发现相互独立的子问题的。
现在,我们回到这道题。假设我们选择了一个圆,将剩余的圆分成两组,使得其互不干扰,能做到吗?
答案是可以,因为一个圆的边界会自然地切开空间。也就是说,我们选择了一个圆,然后只有两种圆还可以选,一种在圆内(包含或者内切),一种在圆外(相离或外切),而这两种是 互不干扰 的。
话说,我们可以先将圆转化为其直径,问题就变成了选择很多线段。但是后文我可能仍然称呼其为圆。
为了让圆内、圆外这两组的分布不那么离谱,我们将所有的圆按照左端点排序。我们每次只决策最靠左的一个圆,就会让剩余的圆的分布自然而美好。对于左端点相同的,我们自然是应该先决策右端点大的。然后剩下的圆就可以分成两部分,依据左端点是否小于当前圆的右端点。
但是,我们很悲伤的发现,我们没有完全摆脱选择的这个圆对别人的约束。我们得把它塞到状态里。我们可以写出一个 f ( i , j , x ) f(i,j,x) f(i,j,x) 表示,第 i i i 到第 j j j 个圆可选,右端点不可以超过 x x x 。这是 O ( n 3 ) \mathcal O(n^3) O(n3) 的。怎么办?
突然发觉, j j j 和 x x x 有必然的联系。因为我们的 j j j 是左端点小于 x x x 的圆中,最右的一个。如果二者有必然联系,我们可不可以试着将二者用一个值来表示?
用 f ( i , j ) f(i,j) f(i,j) 表示,从第 i i i 个圆开始,考虑所有左端点不超过 j j j 的圆,且不能选择包含 j j j 的圆。这里的 j j j 明显只有 n n n 个取值了,就是每个圆的右端点。这样就完全可做了!
用 R i R_i Ri 表示, ∀ \forall ∀ 圆,左端点不超过第 i i i 个圆的右端点,最靠右的一个。若圆 i i i 右端点为 m i m_i mi ,那么
f ( i , j ) = max [ f ( i + 1 , j ) , f ( i + 1 , m i ) + f ( R i + 1 , j ) + 1 ] f(i,j)=\max[f(i+1,j),f(i+1,m_i)+f(R_i+1,j)+1] f(i,j)=max[f(i+1,j),f(i+1,mi)+f(Ri+1,j)+1]
左边表示不选,右边表示选。注意,这个圆本身也要满足不能包含 j j j ,所以 m i ≤ j m_i\le j mi≤j 。
R i R_i Ri 可以用 O ( n log n ) \mathcal O(n\log n) O(nlogn) 预处理, j j j 和 m i m_i mi 可以 O ( n log n ) \mathcal O(n\log n) O(nlogn) 压缩至 [ 1 , n ] [1,n] [1,n] ,所以总复杂度是 O ( n 2 ) \mathcal O(n^2) O(n2) 。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
inline int readint(){
int a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
template < class T >
void getMax(T&a,T b){ if(a < b) a = b; }
const int MaxN = 2001;
// dp[i][j]:从第i个开始,直到左端点不小于R[j]
// R[j]:将所有右端点取出来排序
int dp[MaxN][MaxN], R[MaxN];
struct Range{
int l, r, id;
bool operator < (const Range &that) const {
if(l != that.l)
return l < that.l;
return r > that.r;
}
} node[MaxN];
// Rid:该区间右端点对应哪个R
int Rid[MaxN], nxt[MaxN], n;
void output(int i,int j){
if(i == n) return ;
if(dp[i][j] == dp[i+1][j])
return output(i+1,j);
printf("%d ",node[i].id+1);
output(i+1,Rid[i]), output(nxt[i],j);
}
int main(){
n = readint();
for(int i=0; i<n; ++i){
int o = readint(), x = readint();
node[i].l = o-x, node[i].r = o+x;
node[i].id = i, R[i] = node[i].r;
}
sort(node,node+n), sort(R,R+n);
for(int i=0; i<n; ++i)
for(int j=0; j<n; ++j)
if(node[i].r == R[j])
Rid[i] = j;
for(int i=0; i<n; ++i){
nxt[i] = n;
for(int j=0; j<n; ++j)
if(node[i].r <= node[j].l){
nxt[i] = j; break;
}
}
for(int i=n-1; ~i; --i)
for(int j=0; j<n; ++j){
dp[i][j] = dp[i+1][j]; // 不选
if(node[i].r <= R[j]) // 选
getMax(dp[i][j],dp[i+1][Rid[i]]
+dp[nxt[i]][j]+1);
}
printf("%d\n",dp[0][n-1]), output(0,n-1);
return 0;
}