【 Educational Codeforces Round 61 (Rated for Div. 2) G. Greedy Subsequences】单调栈+dfs序+线段树

18 篇文章 0 订阅
7 篇文章 0 订阅

G. Greedy Subsequences

题意

给你一个长度为n的数组,对其中每个长度为k的连续子序列求这个子序列的最长贪心子序列
选定一个数作为第一个数,那么他右面离他最近而且比他大的数作为第二个数,以此类推直到不能再加数这样产生的序列被称为贪心子序列。
对于一个序列选定每一个数作为起点,得到的最长的贪心子序列就是一个序列的最长贪心子序列。

1 ≤ n , k ≤ 1 0 6 1 \leq n,k \leq 10^6 1n,k106
做法

首先这道题一定是滑动窗口来做的。也就是算出第一段的贡献之后,加上一个数的贡献,减去一个数的贡献。
可是我们怎么维护好一些呢,由于要计算每个数靠右的第一个比他大的数,我们这个过程用一个单调栈就能做完。
之后我们把每个数和右边第一个比他大的数建边,我们就可以得到一个森林,再把所有的根同时指向一个虚根,就得到一颗虚树。
我们设树上每个点的权值代表他到虚根这条路径上有多少个点,那么所有点中最大的权值就是答案,那么我们怎么维护加入一个点呢,我们发现,加入一个点的贡献就是把包括他在内的子树中所有点的权值+1,这个用一个基于dfs序的线段树就可以维护,删除一个点的贡献同理,只要将他的子树内所有点的权值-1即可。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;
#define pb push_back
const int maxn = 1e6+5;
struct T
{
    int l,r,mid;
    int maxx,add;
}tree[maxn<<2];
int in[maxn],ou[maxn];
int a[maxn];
int fa[maxn];
vector<int> G[maxn];
int tim;
void dfs(int rt,int fa)
{
    in[rt]=++tim;
    for(int i=0;i<G[rt].size();i++)
    {
        int to=G[rt][i];
        if(to==fa) continue;
        dfs(to,rt);
    }
    ou[rt]=tim;
}
void push_up(int rt)
{
    tree[rt].maxx=max(tree[rt<<1].maxx,tree[rt<<1|1].maxx);
}
void push_down(int rt)
{
    if(tree[rt].add!=0)
    {
        int tmp=tree[rt].add;
        tree[rt<<1].maxx+=tmp;
        tree[rt<<1|1].maxx+=tmp;
        tree[rt<<1].add+=tmp;
        tree[rt<<1|1].add+=tmp;
        tree[rt].add=0;
    }
}
void build(int rt,int l,int r)
{
    tree[rt].l=l;
    tree[rt].r=r;
    tree[rt].add=0;
    if(l==r)
    {
        tree[rt].maxx=0;
        return ;
    }
    int mid=tree[rt].mid=l+r>>1;
    build(rt<<1,l,mid);
    build(rt<<1|1,mid+1,r);
    push_up(rt);
    return ;
}
void update(int rt,int l,int r,int val)
{
    if(tree[rt].l>r||tree[rt].r<l) return ;
    if(tree[rt].l>=l&&tree[rt].r<=r)
    {
        tree[rt].add+=val;
        tree[rt].maxx+=val;
        return ;
    }
    push_down(rt);
    if(tree[rt].mid>=l) update(rt<<1,l,r,val);
    if(tree[rt].mid<r) update(rt<<1|1,l,r,val);
    push_up(rt);
}
int ans[maxn];
int main()
{
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    build(1,1,n+1);
    stack<int>st;
    for(int i=n;i>=1;i--)
    {
        while(!st.empty()&&a[st.top()]<=a[i]) st.pop();
        if(st.empty()) G[0].pb(i);
        else G[st.top()].pb(i);
        st.push(i);
    }
    dfs(0,-1);
    for(int i=n;i>=n-k+1;i--) update(1,in[i],ou[i],1);
    ans[n-k+1]=tree[1].maxx;
    for(int i=n-k;i>=1;i--)
    {
        update(1,in[i+k],ou[i+k],-1);
        update(1,in[i],ou[i],1);
        ans[i]=tree[1].maxx;
    }
    for(int i=1;i<=n-k+1;i++) printf("%d ",ans[i]);
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值