codeplus2017 Yazid的新生舞會
Yazid的新生舞會 codeplus2017 題解
受到 type 是 \(1\) 和 \(3\) 的啟發,我們可以列舉 \(num\) 為眾數,然後把每一個的答案相加。
假設我們正在計算一個眾數,計算它字首的出現次數 \(s_i\),那合法的情況要滿足 \(s_r-s_l>\frac{r-l}{2}\),即 \(2s_r-r>2s_l-l\)。這可以看成逆序對問題,可以用樹狀陣列維護,查詢 \(2s_i-i-1\),插入 \(2s_i-i\)。
我們繼續研究,如果 \(i\) 是第一次出現 \(num\) 的位置,\(j\) 是第二次出現的位置。那 \(k\in i\sim j-1\)
所以我們可以先統一查詢 \(i\sim j-1\) 中的詢問,然後統一修改。修改很容易,給 \(2s_i-i\) 到 \(2s_i-j+1\) 中的每一個加上 \(1\),用樹狀陣列打差分標記即可。
所以主要的問題在於查詢。每一個 \(ask(2s_k-k-1)\) 都是樹狀陣列差分標記的字首和,而我們計算 \(i\sim j-1\) 中的每一個的答案又是 \(ask\) 的字首和,即 \(\sum\limits_{l=1}^{s_k-k-1} ask(l)\)
下面我們來求一下三階字首和。即 \(d\) 為原陣列,\(c\) 是 \(d\) 的字首和,\(b\) 是 \(c\) 的字首和,\(a\) 是 \(b\) 的字首和。那 \(a\) 就是 \(d\) 的字首和。
\(a_n=\sum\limits_{i,j,k}d_k\)
我們來計算對於每一個 \(d_k\) 在 \(a_n\) 中出現了多少次。
\(k=1\) 時,\(j\) 可以取 \(1\sim n\)。\(j\) 取 \(1\) 時,\(i\) 可以取 \(1\sim n\);$j $ 取 \(2\) 時,\(i\) 可以取 \(2\sim n\)。以此類推,共有 \(\frac{(n+1)n}{2}\) 次。
同理 \(k=2\) 時,共有 \(\frac{n(n-1)}{2}\) 次。
最後整合一下,\(d_k\) 出現的次數是 \(\frac{(n+2-k)(n+1-k)}{2}\)。
\(a_n=\sum\limits_{k=1}^n \frac{(n+2-k)(n+1-k)}{2}d_k\)。
把括號拆開,用樹狀陣列維護一下 \(k\) 的每一項即可。
\(a_n=\sum\limits_{k=1}^n \frac{(n+2-k)(n+1-k)}{2}d_k=\frac{(n+2)(n+1)}{2}\sum\limits_{k=1}^nd_k-\frac{2n+3}{2}\sum\limits_{k=1}^nd_k*k+\frac{1}{2}\sum\limits_{k=1}^{n}d_k*k^2\)
這是對於一個 \(num\) 的情況,我們列舉每個 \(num\),然後按每一段進行計算,整體複雜度是 \(O(\sum size\times\log n)=O(n\log n)\) 的。
下面是 AC 程式碼 & 註釋
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
const long long maxn=1e6+5;
const long long inf=0x3f3f3f3f;
long long n,op,ans,c1[maxn],c2[maxn],c3[maxn];
vector<long long> a[maxn];
long long lowbit(long long x)
{
return x&(-x);
}
void add(long long x,long long nm)
{
for(long long i=x;i<=2*n+1;i+=lowbit(i))
{
c1[i]+=nm;
c2[i]+=nm*x;
c3[i]+=nm*x*x;
}
}
long long sum(long long x)
{
long long res=0;
for(long long i=x;i>=1;i-=lowbit(i))
res+=c1[i]*(x+1)*(x+2)-c2[i]*(2*x+3)+c3[i];
return res/2;
}
int main()
{
freopen("party.in","r",stdin);
freopen("party.out","w",stdout);
cin>>n>>op;
for(long long i=1;i<=n;i++)
{
long long x;
cin>>x;
a[x+1].push_back(i);
}
for(long long num=1;num<=n;num++)
{
if(a[num].empty())
continue;
a[num].push_back(n+1);
for(long long i=0;i<a[num].size();i++) //i就是出現次數
{
//y是上文第一次出現的i,x是上文的j
//這裡加上n+1是偏移量,把[-n,n]平移到[1,2n+1]
long long y=2*i-(i==0?0:a[num][i-1])+n+1;
long long x=2*i-(a[num][i]-1)+n+1;
//查詢 A(t-1)的權值和, 其中t在[x,y]範圍內,
ans+=sum(y-1)-sum(x-2);
add(x,1);
add(y+1,-1);
}
for(long long i=0;i<a[num].size();i++) //清空樹狀陣列
{
long long y=2*i-(i==0?0:a[num][i-1])+n+1;
long long x=2*i-(a[num][i]-1)+n+1;
add(x,-1);
add(y+1,1);
}
}
cout<<ans<<endl;
return 0;
}