hdu 6991 / 2021“MINIEYE杯”中国大学生算法设计超级联赛(4)1007 Increasing Subsequence(CDQ分治优化dp)
https://acm.hdu.edu.cn/showproblem.php?pid=6991
题意:
给一个n的排列, 问有多少个极长上升子序列
设f[i]表示以i结尾的极长上升子序列个数
初始化:若第i个数是前i个数里最小的,则f[i]=1
j对i有贡献,当且仅当不存在k,满足j<k<i 且 a[j]<a[k]<a[i]
最后若i后面没有比a[i]大的数,那就可以累积f[i]的答案
这是一个n^2的做法
可以用CDQ分治优化这个做法
考虑左边一半的数对右边一半的数的贡献
对于左边的j和右边的i,j对i有贡献,当且仅当不存在k,满足j<k<i 且 a[j]<a[k]<a[i]
所以维护左右2个单调栈,按数值从小到大的顺序往单调栈里加元素
对于左边的一半数,维护下标位置单调递减
因为是按数值从小到大插入的,在后面插入的数必须出现在前面的数的前面,才能保证这个树不会成为上面那个k
对于右边的一半数,维护下标位置单调递增
因为左边的单调栈保证了不会有左边一半的数成为满足a[k]>a[j]的k,还需要右边一半的数不能出现满足要求的k
又因为是按从小到大插入的,所以小于a[i]的条件一定满足,那就只能不允许存在a[k]>a[j]
还是因为是按从小到大插入的,所有左右两个单调栈内的元素值都是单调递增的
所以就要求左边的数要大于当前插在右边单调栈的数的栈里的前一个数
可以二分在左边找到满足要求的数的范围
左边的栈统计前缀和来计算答案
可以在最后面加一个n+1
然后直接统计以n+1结尾的极长上升子序列即可
#include<bits/stdc++.h> using namespace std; #define N 100005 struct node { int id,val; bool operator < (const node p) { return val<p.val; } }e[N]; const int mod=998244353; int dp[N],f[N]; node stl[N],str[N]; int topl,topr; bool cmp(node p,node q) { return p.id<q.id; } void solve(int l,int r) { if(l==r) return; int mid=l+r>>1; solve(l,mid); sort(e+l,e+r+1); topl=topr=0; for(int i=l;i<=r;++i) if(e[i].id<=mid) { while(topl && e[i].id>stl[topl].id) topl--; stl[++topl]=e[i]; f[topl]=(f[topl-1]+dp[e[i].id])%mod; } else { while(topr && e[i].id<str[topr].id) topr--; if(stl) { int p=lower_bound(stl+1,stl+topl+1,str[topr])-stl; if(p<=topl) dp[e[i].id]=(dp[e[i].id]+(f[topl]-f[p-1]+mod)%mod)%mod; } str[++topr]=e[i]; } sort(e+l,e+r+1,cmp); solve(mid+1,r); } int main() { int T,n,x,mi; scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=1;i<=n;++i) { scanf("%d",&e[i].val); e[i].id=i; } ++n; e[n].id=e[n].val=n; mi=n; for(int i=1;i<=n;++i) if(e[i].val<mi) { dp[i]=1; mi=e[i].val; } else dp[i]=0; solve(1,n); printf("%d\n",dp[n]); } }