「NNEZ Monthly Round 6」LIS
20分
首先考虑Ai>0的情况
显然有dp方程:
fi=max1≤j<i{fj+1}(Aj<Ai)
边界条件f[i]=1
时间复杂度O(n2),期望得分20分
50分
50分(100分)的数据要求我们在O(nlogn)的时间复杂度内解决这个问题
定义序列Di表示对于所有满足fk=i的k中,Ak最小是多少
Di=minfk=i{Ak}
显然序列D是单调递增的(想一想,为什么)
我们来考虑如何维护这个序列D
假设当前序列D的长度为len,当前扫描到的元素为Ai,分两种情况:
- 如果Ai>Dlen,那么令Dlen+1=Ai,同时令len=len+1,意义为将Ai接到以Dlen为结尾的上升子序列后面
- 如果Ai≤Dlen,那么在序列D中二分找到第一个p满足Dp≥Ai,令Dp=Ai,意义为把以Dp为结尾的上升子序列的末尾元素(即Dp)换成Ai
tips:
lowerbound(D+1,D+len+1,x)-D
可以得到D数组中第一个大于等于x的位置是第几个
我们来思考一下第二种情况的意义,对于一个上升子序列,我们自然希望在它后面能接更多的元素,那么这个子序列的末尾元素当然是越小越好
扫描完整个序列A后,LIS的长度即为序列D的长度len
时间复杂度O(nlogn),至此期望得分50分
100分
考虑Ai可能会等于0的情况
首先,对于一个末尾为X的上升子序列,如果我们既可以在它后面接一个0,也可以接一个Ai(即Ai>X),那么显然接0一定不会使答案更劣
所以一个包含Ai中所有0的上升子序列,一定不会比最长上升子序列短
那么我们可以先把所有的0塞进答案子序列里,然后再塞非零的元素
对于剩下的非零元素,我们先考虑其中两个元素Ai,Aj(i<j),设它们之间0的个数为k
由于我们必须要保证上升子序列中的元素严格递增,并且要保证已经塞进上升子序列中的0能够指派一个值,所以如果Ai和Aj能够被同时塞进答案子序列里,必须要满足:
Ai+k<Aj
我们定义一个函数g(i)表示位置i前面0的个数,定义Bi=Ai−g(i)
那么我们可以塞进答案子序列里的非零元素个数即为序列B的LIS长度
证明:
对于任意i,j满足i<j
Bi<Bj⇔Ai−g(i)<Aj−g(j)⇔Ai+[g(j)−g(i)]<Aj⇔Ai+k<Aj
最终的答案是序列B的LIS长度加上序列A中0的个数
二分求LIS,期望得分100分
需要注意的是,由于我们已经提前考虑了0,所以在计算序列B的LIS长度时,需要跳过Ai=0的位置
Code
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
| #include <cstdio> #include <algorithm> using namespace std;
const int maxn=100005;
int n; int a[maxn]; int d[maxn],len;
int main() { scanf("%d",&n); int zero=0; d[0]=-0x7f7f7f7f; for(int i=1;i<=n;++i) { scanf("%d",&a[i]); if(a[i]==0) ++zero; else { a[i]-=zero; if(a[i]>d[len]) d[++len]=a[i]; else { int p=lower_bound(d+1,d+len+1,a[i])-d; d[p]=a[i]; } } } printf("%d",len+zero); }
|
谢罪
之前月赛讲题的时候,条理不甚清晰,错漏诸多,所以特地重新写了一篇自认为相对更加详细严谨的题解
非常抱歉,我什么都会做的(土下座)