1. 程式人生 > 其它 >codeplus2017 Yazid的新生舞會

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\)

\(s_k\) 都相等。但 \(k\) 是遞增的,所以每次查詢的 \(2s_k-k-1\) 是遞減的,但每次修改是 \(2s_k-k\),所以在 \(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)\)

,記為 \(A(i)\),我們可以在再設 \(S(i)=\sum A(x)\),那我們求的就是 \(S(j-1)-S(i-2)\)。這樣,\(S(i)\) 就是樹狀陣列差分標記的三階字首和。為啥這裡是i-2呢?因為逆序對!我們查詢是到它-1對叭!我們要查詢 \(A(t-1)\)的權值和, 其中 t 在\([i,j]\)範圍內,

下面我們來求一下三階字首和。即 \(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;
}