1. 程式人生 > >LOJ6253:「CodePlus 2017 11 月賽」Yazid 的新生舞會 (線段樹)

LOJ6253:「CodePlus 2017 11 月賽」Yazid 的新生舞會 (線段樹)

題目分析:這題是我做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]中的所有點作為右端點對答案的貢獻為r

l+1i=1Sl1i1j=cnt[j],其中cnt[j]表示在區間[0,l1]之間字首和為j的端點數目;在統計這段區間的答案後,我們還需要對區間[Si1(rl+1),Si11]中的所有cnt均進行+1操作。顯然地,我們使用一個維護BiCi=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;
}