[BZOJ1852] [MexicoOI06]最长不下降序列(贪心)

考虑如下贪心

(我将问题反过来考虑,也就是要满足$A_i > \max_{j=1}^{j < i} {B_j}$)

首先对于读入的$(A,B)$,按照$B$的值递增排序

(选出的答案序列不一定是其中一个有序的子序列)

答案序列存在若干个$B$递增的位置,设它们是$\{a_i\},a_{i-1}<a_i$

合法的递增序列需要满足的限制是$A_{a_i}>B_{a_{i-1} }$

考虑剩下的部分即$j\in[a_{i-1}+1,a_i-1]$,那么这些点放在$a_i$后面一定是最优的(因为此时不会改变最大的$B$),此时限制它们的$B$就是$B_{a_i}$

即这一部分中满足$A_j>B_{a_i}$的$j$均可以选出来

为了便于表示,设$C(l,r)=|\{i|i\in[l,r],A_i>B_{r+1} \}|$,可以通过一个主席树维护


定义$dp_i$表示当前以$i$为最大的$B$的答案,特别的,$dp_0$表示序列为空$A_0=B_0=-\infty$

枚举每个$i$进行转移

朴素的转移就是可以枚举上一个位置$j$,$O(n^2)$转移

需要找到前面第一个能把$i$接上去的$j$即可,即第一个$B_r<A_i(j\ge 0)$的位置,那么合法的决策位置就是$[0,r]$

则$dp_i=\max_0^r\{dp_j+1+C(j+1,i-1)\}$

设最优决策点为$k\in[0,r]$,影响最优决策点位置的有两个方面

从$[j+1,i-1]$这一段点考虑,$j$越小时,就会有越多的点对被$B_i$限制,也就是说$j$越大,对于中间这一段来说越优

但是从$dp_j$的角度考虑,并不是$j$越大越好,因为可能存在一个$A_j$特别小限制了前面递增点列的选择

推论:如果前面存在一个$A_j>B_i$,那么$k\ge j$

事实上应该说成$dp_j+C(j+1,i-1)\ge\forall d\in[0,j-1],dp_d+C(d+1,i-1)$

综合上面两条来看,如果$A_j>B_i$意味着把它放进递增序列里绝对是优的,因为不会对前面的点产生不良限制

消除了不良限制之后,就满足最优性了

所以发现最优决策点的范围缩小到了$[l=max\{k|A_k>B_i\},r]$

发现决策范围内的$C(l+1,i-1)=C(l+2,i-1)=\cdots=C(r+1,i-1)$

所以$dp_i=\max_l^r\{dp_j\}+C(r+1,i-1)=max_0^r\{dp_j\}+C(r+1,i-1)$

所以可以直接维护一个前缀最大值,每次二分找到那个$r$,求出$C(r+1,i-1)$即可

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
57
58
59
const int N=2e5+10,K=15;

int n;

struct Node{
int a,b;
bool operator < (const Node __) const {
return b<__.b;
}
}A[N];
int h[N],hc;
int dp[N];

int rt[N*K],s[N*K],ls[N*K],rs[N*K],cnt;
void Add(int p,int pre,int l,int r,int x) {
s[p]=s[pre]+1;
if(l==r) return;
int mid=(l+r)>>1;
ls[p]=ls[pre],rs[p]=rs[pre];
x<=mid?Add(ls[p]=++cnt,ls[pre],l,mid,x):Add(rs[p]=++cnt,rs[pre],mid+1,r,x);
}

int Que(int pl,int pr,int l,int r,int ql,int qr) {
if(ql>qr) return 0;
if(ql==l&&qr==r) return s[pr]-s[pl];
int mid=(l+r)>>1;
if(qr<=mid) return Que(ls[pl],ls[pr],l,mid,ql,qr);
else if(ql>mid) return Que(rs[pl],rs[pr],mid+1,r,ql,qr);
else return Que(ls[pl],ls[pr],l,mid,ql,mid)+Que(rs[pl],rs[pr],mid+1,r,mid+1,qr);
}

int main(){
rep(i,1,n=rd()) {
A[i].a=rd(),A[i].b=rd();
h[++hc]=A[i].a,h[++hc]=A[i].b;
}
sort(h+1,h+hc+1),hc=unique(h+1,h+hc+1)-h-1;
sort(A+1,A+n+1);
rep(i,1,n) {
A[i].a=lower_bound(h+1,h+hc+1,A[i].a)-h;
A[i].b=lower_bound(h+1,h+hc+1,A[i].b)-h;
Add(rt[i]=++cnt,rt[i-1],1,hc,A[i].a);
}
rep(i,1,n) {
int l=1,r=i-1,res=0;
while(l<=r) {
int mid=(l+r)>>1;
if(A[mid].b<A[i].a) l=mid+1,res=mid;
else r=mid-1;
}
dp[i]=max(dp[i-1],dp[res]+1+Que(rt[res],rt[i-1],1,hc,A[i].b+1,hc));
}
int ans=dp[n];
printf("%d\n",ans);
}




附:离线做法

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;

#define reg register
typedef long long ll;
#define rep(i,a,b) for(reg int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(reg int i=a,i##end=b;i>=i##end;--i)

char IO;
int rd(){
int s=0,f=0;
while(!isdigit(IO=getchar())) f|=(IO=='-');
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=2e5+10,K=15;

int n;
struct Node{
int a,b;
bool operator < (const Node __)const{ return b<__.b; }
}A[N];
struct Query{
int x,p,id,k;
bool operator < (const Query __)const{ return p<__.p; }
}Q[N<<1];

int qc,h[N],hc,dp[N];
int L[N],Ans[N];

int s[N];
void Add(int p,int x){ while(p) s[p]+=x,p-=p&-p; }
int Que(int p){
int res=0;
while(p<=hc) res+=s[p],p+=p&-p;
return res;
}

int main(){
rep(i,1,n=rd()) {
A[i].a=rd(),A[i].b=rd();
h[++hc]=A[i].a,h[++hc]=A[i].b;
}
sort(h+1,h+hc+1),hc=unique(h+1,h+hc+1)-h-1;
sort(A+1,A+n+1);
rep(i,1,n) {
A[i].a=lower_bound(h+1,h+hc+1,A[i].a)-h;
A[i].b=lower_bound(h+1,h+hc+1,A[i].b)-h;
}
rep(i,1,n) {
int l=1,r=i-1,res=0;
while(l<=r) {
int mid=(l+r)>>1;
if(A[mid].b<A[i].a) l=mid+1,res=mid;
else r=mid-1;
}
L[i]=res;
if(i-1>L[i]) Q[++qc]=(Query){A[i].b+1,i-1,i,1},Q[++qc]=(Query){A[i].b+1,L[i],i,-1};
}
sort(Q+1,Q+qc+1);
int p=1;
rep(i,0,n) {
if(i) Add(A[i].a,1);
while(p<=qc && Q[p].p<=i) Ans[Q[p].id]+=Q[p].k*Que(Q[p].x),p++;
}
rep(i,1,n) dp[i]=max(dp[i-1],dp[L[i]]+1+Ans[i]);
int ans=dp[n];
printf("%d\n",ans);
}