[Codeforces1132G]Greedy Subsequences——线段树+单调栈
题目链接:
题目大意:给定一个序列$a$,定义它的最长贪心严格上升子序列为$b$满足若$a_{i}$在$b$中则$a_{i}$之后第一个比它大的也在$b$中。给出一个数$k$,求出所有长度为$k$的子区间的最长贪心严格上升子序列。
考虑如果选取一个数之后一定会选取它之后第一个比它大的数,那么我们将每个数与它右边第一个比他大的数连边,这样我们就得到了一个森林,再建立一个虚拟节点并将森林中所有根都连向他就得到了一棵树。对于整个序列来说选取了一个点就会选取这个点在树上到根路径上的所有点,而整个序列的答案就是每个点深度的最大值。现在考虑一个子区间的答案,当区间右端点右移时,新加入区间的这个数会对原区间中比这个数小的数的答案$+1$也就是将这个数在树上的子树中所有点的答案$+1$(这些答案$+1$的所有点中虽然包括区间之前的数但显然这些数的答案不会比区间内数的答案更大,最多只会与最大值相同),同样当区间左端点右移时,就将这个数在树上的子树中所有点的答案$-1$来确保区间之前的数的答案不会比区间中数的答案更优。我们对整棵树的$dfs$序维护线段树,每次询问只需要分别将左右端点右移然后在线段树上区间修改并查询全局最大值即可。至于每个数右边第一个大于它的数只需要维护一个单调递减的单调栈然后每次弹栈时将栈顶与当前数连边即可。
#include<set> #include<map> #include<queue> #include<stack> #include<cmath> #include<cstdio> #include<vector> #include<bitset> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int st[1000010]; int top; int s[1000010]; int t[1000010]; int mx[4000010]; int sum[4000010]; int head[1000010]; int to[2000010]; int nex[2000010]; int n,k; int a[1000010]; int dfn; int tot; void add(int x,int y) { nex[++tot]=head[x]; head[x]=tot; to[tot]=y; } void dfs(int x) { s[x]=++dfn; for(int i=head[x];i;i=nex[i]) { dfs(to[i]); } t[x]=dfn; } void pushup(int rt) { mx[rt]=max(mx[rt<<1],mx[rt<<1|1]); } void pushdown(int rt) { if(sum[rt]) { sum[rt<<1]+=sum[rt]; sum[rt<<1|1]+=sum[rt]; mx[rt<<1]+=sum[rt]; mx[rt<<1|1]+=sum[rt]; sum[rt]=0; } } void change(int rt,int l,int r,int L,int R,int x) { if(L<=l&&r<=R) { sum[rt]+=x; mx[rt]+=x; return ; } pushdown(rt); int mid=(l+r)>>1; if(L<=mid) { change(rt<<1,l,mid,L,R,x); } if(R>mid) { change(rt<<1|1,mid+1,r,L,R,x); } pushup(rt); } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); while(top&&a[st[top]]<a[i]) { add(i,st[top]); top--; } st[++top]=i; } while(top) { add(n+1,st[top]); top--; } dfs(n+1); for(int i=1;i<=k;i++) { change(1,1,n+1,s[i],t[i],1); } printf("%d ",mx[1]); for(int i=k+1;i<=n;i++) { change(1,1,n+1,s[i],t[i],1); change(1,1,n+1,s[i-k],t[i-k],-1); printf("%d ",mx[1]); } }