LOJ6253:「CodePlus 2017 11 月赛」Yazid 的新生舞会 (线段树)

题目传送门:https://loj.ac/problem/6253


题目分析:这题是我做CodePlus11月月赛的时候见到的,当时由于TUOJ太卡,一直被无法提交的问题困扰。导致我写完前两题正解后也没有再写T3T4的暴力。不过我还是看了一下题面,赛后研究了挺久,结果发现还是不会做QAQ(虽然80分的暴力并不需要怎么动脑子)。看了题解后才发现这是道数据结构好题。

一种可行的思路是:枚举一个值(如果序列中有这个值的话),考虑让这个值成为合法区间中出现次数超过一半的数。将序列中所有等于这个值的位置标为1,其余位置标为-1。那么如果一段区间的和大于0,它就是合法区间。换句话说,如果我们做出前缀和,那么对于任意一个1<=i<=n, sum[j]<sum[i](0<=j<i) 的个数就可以贡献答案。

虽然上面的计算可以用线段树维护,但这样总时间就达到了 O(cntnlog(n)) ,其中cnt是不同数字的个数。如果我们每一次操作的时间都能和1的个数相关的话,就可以将时间降到稳定的 O(nlog(n)) 。这就意味着我们要一次性处理完一段连续的-1区间对答案的贡献。这也就是解题的关键和难点(我自己想这题的时候就是被卡在这里)。

不妨先画个栗子(虽然这张图其实并没有什么用):

现在我们要一次性处理红色括号内的-1对答案的贡献。也就是区间右端点R在这些-1里的时候,有多少个合法的左端点L。假设到红括号之前为止,前面的数前缀和为sum。那么对于第一个-1,红括号之前有多少个sum[L-1]属于(-oo,sum-2],它就对答案有多少贡献;对于第二个-1,红括号之前有多少个sum[L-1]属于(-oo,sum-3],它就对答案有多少贡献(为什么不算进第一个-1,因为很明显左端点L不可能取这个-1);依此类推……。

假设连续的-1的长度为len,那么现在的贡献就变成了:[-n,sum-len-1]的个数被加了len次(因为前缀和最小也就是-n),[sum-len,sum-2]中的数i被加了sum-i-1次。于是我们将权值线段树开出来,维护一个num[i]与i*num[i]的区间和(num[i]表示值为i的前缀和个数),然后加加减减一下就行。处理完这段-1后,再对[sum-1,sum-len]这段区间的num+1。

按照题解的原话来说就是:

我们考虑取出所有 B 中的极长1子区间,观察这些区间中的所有点作为右端点对答案的贡献。不难发现极长 1 子区间 [l,r] 中的所有点作为右端点对答案的贡献为 rl+1i=1Sl1i1j=cnt[j] ,其中 cnt[j] 表示在区间 [0,l1] 之间前缀和为 j 的端点数目;在统计这段区间的答案后,我们还需要对区间[Si1(rl+1),Si11]中的所有 cnt 均进行 +1 操作。显然地,我们使用一个维护 Bi Ci=i×Bi 的线段树就可以支持这些查询、修改操作。于是我们使用这棵线段树维护相关信息,并从左到右枚举右端点,统计答案即可。


CODE:

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=500100;
typedef long long LL;

struct Tnode
{
    int add,sum;
    LL val,cnt;
    bool empty;

    void Clear()
    {
        add=sum=cnt=0;
        empty=true;
    }

    void Add(int v,int len)
    {
        add+=v;
        sum+=(v*len);
        cnt+=( (long long)v*val );
    }
} tree[maxn<<3];

struct data
{
    int num,id;
} a[maxn];
int n,m;

bool Comp(data x,data y)
{
    return x.num<y.num || ( x.num==y.num && x.id<y.id );
}

void Build(int root,int L,int R)
{
    if (L==R)
    {
        tree[root].val=L;
        return;
    }

    int Left=root<<1;
    int Right=Left|1;
    int mid=(L+R)>>1;

    Build(Left,L,mid);
    Build(Right,mid+1,R);
    tree[root].val=tree[Left].val+tree[Right].val;
}

void Down(int root,int L,int R)
{
    int Left=root<<1;
    int Right=Left|1;
    int mid=(L+R)>>1;

    if (tree[root].empty)
    {
        tree[Left].Clear();
        tree[Right].Clear();
        tree[root].empty=false;
    }

    if (tree[root].add)
    {
        int &v=tree[root].add;
        tree[Left].Add(v,mid-L+1);
        tree[Right].Add(v,R-mid);
        v=0;
    }
}

void Update(int root,int L,int R,int x,int y,int v)
{
    if ( y<L || R<x ) return;
    if ( x<=L && R<=y )
    {
        tree[root].Add(v,R-L+1);
        return;
    }

    Down(root,L,R);

    int Left=root<<1;
    int Right=Left|1;
    int mid=(L+R)>>1;

    Update(Left,L,mid,x,y,v);
    Update(Right,mid+1,R,x,y,v);

    tree[root].sum=tree[Left].sum+tree[Right].sum;
    tree[root].cnt=tree[Left].cnt+tree[Right].cnt;
}

LL Query(int root,int L,int R,int x,int y,int v)
{
    if ( y<L || R<x ) return 0;
    if ( x<=L && R<=y )
        if (v==1) return tree[root].sum;
        else return tree[root].cnt;

    Down(root,L,R);

    int Left=root<<1;
    int Right=Left|1;
    int mid=(L+R)>>1;

    LL vl=Query(Left,L,mid,x,y,v);
    LL vr=Query(Right,mid+1,R,x,y,v);
    return (vl+vr);
}

void Work(int last,int now,int &v,LL &ans)
{
    int len=now-last-1,L=v-len-1,R=v-2;
    if (n+R>=1) ans+=( Query(1,1,2*n,1,n+R,1)*(long long)len );
    if (L+1<=R)
    {
        ans-=Query(1,1,2*n,n+L+1,n+R,2);
        ans+=( Query(1,1,2*n,n+L+1,n+R,1)*(long long)(n+L) );
    }
    Update(1,1,2*n,n+L+1,n+R+1,1);
    v-=len;
}

int main()
{
    freopen("singledog.in","r",stdin);
    freopen("singledog.out","w",stdout);

    scanf("%d%d",&n,&m);
    for (int i=1; i<=n; i++) scanf("%d",&a[i].num),a[i].id=i;
    sort(a+1,a+n+1,Comp);
    Build(1,1,2*n);

    int head=1;
    LL ans=0;
    while (head<=n)
    {
        int tail=head;
        while ( tail<n && a[tail+1].num==a[head].num ) tail++;
        int last=0,v=0;
        Update(1,1,2*n,n,n,1);

        for (int i=head; i<=tail; i++)
        {
            int now=a[i].id;
            if (last+1<now) Work(last,now,v,ans);
            v++;
            if (n+v-1>=1) ans+=Query(1,1,2*n,1,n+v-1,1);
            Update(1,1,2*n,n+v,n+v,1);
            last=now;
        }

        if (last<n) Work(last,n+1,v,ans);
        tree[1].Clear();
        head=tail+1;
    }
    printf("%lld\n",ans);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值