LOJ6253:「CodePlus 2017 11 月賽」Yazid 的新生舞會 (線段樹)
題目分析:這題是我做CodePlus11月月賽的時候見到的,當時由於TUOJ太卡,一直被無法提交的問題困擾。導致我寫完前兩題正解後也沒有再寫T3T4的暴力。不過我還是看了一下題面,賽後研究了挺久,結果發現還是不會做QAQ(雖然80分的暴力並不需要怎麼動腦子)。看了題解後才發現這是道資料結構好題。
一種可行的思路是:列舉一個值(如果序列中有這個值的話),考慮讓這個值成為合法區間中出現次數超過一半的數。將序列中所有等於這個值的位置標為1,其餘位置標為-1。那麼如果一段區間的和大於0,它就是合法區間。換句話說,如果我們做出字首和,那麼對於任意一個1<=i<=n,
雖然上面的計算可以用線段樹維護,但這樣總時間就達到了
不妨先畫個栗子(雖然這張圖其實並沒有什麼用):
現在我們要一次性處理紅色括號內的-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=1∑Sl−1−i−1j=−∞cnt[j]cnt[j] 表示在區間[0,l−1] 之間字首和為j 的端點數目;在統計這段區間的答案後,我們還需要對區間[Si−1−(r−l+1),Si−1−1] 中的所有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;
}