CF1132G

听说,一个好的Oier都是题目喂出来的。

题目

定义一个序列的最长贪心严格上升子序列为:若选出的子序列为 \(a\),对于其中相邻两项 \(i,j\),不存在 b\(i<k<j\),满足在原序列 \(b\) 中,有 \(b_i<b_k\),换句话说就是选择一个元素后必须选择它之后第一个大于它的元素

给定一个长度为 \(n\) 的序列,同时给定一个常数 \(k\),求该序列的所有长度为 \(k\)的子区间的最长贪心严格上升子序列的长度

数据范围\(10^6\)

解题思路

先想了一个长链剖分的假做法,发现不会处理长儿子,自闭了。

考虑每个点的出度都不超过1,所以他构成了一颗森林

\(f_x\)表示从x开始往上走,最长走多远。

每加入一个点,需要把它的子树内的所有点权值+1

每删除一个点,需要把它的权值变得足够小

线段树维护即可

代码

#include<bits/stdc++.h>
#define now edge[i].v
#define go(x) for(int i=head[x];i;i=edge[i].nxt)
#define ls o<<1,l,mid
#define rs o<<1|1,mid+1,r
using namespace std;
const int sz=1e6+527;
int n,k;
int cnt,T;
int x,y,z;
int head[sz];
int a[sz],ans[sz];
int dfn[sz],lev[sz];
stack<int>s;
struct Edge{
	int v,nxt;
}edge[sz];
struct node{
	int tag,mx;
}tr[sz<<2];
void add(int u,int v){
	edge[++cnt]=(Edge){v,head[u]};
	head[u]=cnt;
}
void update(int o){
	tr[o<<1].tag+=tr[o].tag;
	tr[o<<1|1].tag+=tr[o].tag;
	tr[o<<1].mx+=tr[o].tag;
	tr[o<<1|1].mx+=tr[o].tag;
	tr[o].tag=0;
}
void modify(int o,int l,int r){
	if(x<=l&&r<=y) return (void)(tr[o].mx+=z,tr[o].tag+=z);
	if(tr[o].tag) update(o);
	int mid=(l+r)>>1;
	if(x<=mid) modify(ls);
	if(y>mid) modify(rs);
	tr[o].mx=max(tr[o<<1].mx,tr[o<<1|1].mx);
}
void dfs(int x){
	dfn[x]=++T;
	go(x)
		dfs(now);
	lev[x]=T;
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		while(!s.empty()&&a[s.top()]<a[i]){
			add(i,s.top());
			s.pop();
		}
		s.push(i);
	}
	n++;
	while(!s.empty()){
		add(n,s.top());
		s.pop();
	}
	dfs(n);
	for(int i=1;i<=k;i++){
		x=dfn[i],y=lev[i],z=1;
		modify(1,1,n);
	}
	ans[1]=tr[1].mx;
	for(int i=k+1;i<n;i++){
		x=dfn[i],y=lev[i],z=1;
		modify(1,1,n);
		x=dfn[i-k],y=lev[i-k],z=-1;
		modify(1,1,n);
		ans[i-k+1]=tr[1].mx;
	}
	for(int i=1;i<=n-k;i++) printf("%d ",ans[i]);
}

题目链接

posted @ 2019-08-04 22:19  霞光  阅读(189)  评论(0编辑  收藏  举报