[JZOJ5951] 锋芒毕露 (【CodeChef June Challenge 2014】Sereja and Arcs)【平衡规划】【计数】【树状数组】

35 篇文章 0 订阅
25 篇文章 0 订阅

Description

给定一个长度为n的颜色序列a
求四元组 ( x , y , p , q ) , x &lt; p &lt; y &lt; q , a [ x ] = a [ y ] , a [ p ] = a [ q ] , a [ x ] ̸ = a [ p ] (x,y,p,q),x&lt;p&lt;y&lt;q,a[x]=a[y],a[p]=a[q],a[x]\not =a[p] (x,y,p,q),x<p<y<q,a[x]=a[y],a[p]=a[q],a[x]̸=a[p] 的数量

Solution

以下为了方便说明,用大写字母表示一种颜色, A B A B ABAB ABAB就是一个合法四元组
直接计算比较繁琐,考虑补集转化

如果我们能算出 A B A B + B A A B ABAB+BAAB ABAB+BAAB B A A B BAAB BAAB的数量,那就可以求出 A B A B ABAB ABAB的数量了

考虑计算 A B A B + B A A B ABAB+BAAB ABAB+BAAB,这其实就相当于把 x &lt; p x&lt;p x<p的限制去掉了。

枚举 y y y,计算两边有多少对 ( p , q ) (p,q) (p,q),再减掉y,q相同颜色的,乘上左边x的数量就是答案

计算两边有多少对 ( p , q ) (p,q) (p,q),可以维护增量,从左向右扫的时候,每向右一个位置,相当于将一个位置从右边移到了左边,加上多出来的减去删掉的贡献即可。

这样 O ( n ) O(n) O(n)就算完了

考虑如何计算 B A A B BAAB BAAB

直接用数据结构好像没有什么好的办法,于是往平衡规划的方向来想。

我们设定一个阈值 K K K,出现次数小于等于K的颜色记作Q,大于K的颜色记作P

那么P的总数不超过 n / K n/K n/K,Q的大小的平方和不超过 n K nK nK
对于四元组分类讨论

  • P _ _ P P\_\_P P__P:枚举颜色P,从左到右扫,设当前扫到位置 i i i,i之前P的出现次数为 s [ i ] s[i] s[i],它在颜色a[i]中是第t个,那么明显贡献就是 ∑ j = 1 , a [ j ] = a [ i ] , a [ i ] ! = P i − 1 s [ j ] ∗ ( s i z e [ P ] − s [ i ] ) \sum\limits_{j=1,a[j]=a[i],a[i]!=P}^{i-1}s[j]*(size[P]-s[i]) j=1,a[j]=a[i],a[i]!=Pi1s[j](size[P]s[i])只需要用一个桶记一下i前面每个颜色的j的 s [ ] s[] s[]和即可。时间复杂度 O ( n 2 K ) O({n^2\over K}) O(Kn2)

  • Q P P Q QPPQ QPPQ:同样枚举颜色P,从左到右扫,位置i的贡献就是 ∑ j = 1 , a [ j ] = a [ i ] , a [ i ] ! = P i − 1 ( s [ i ] − s [ j ] 2 ) \sum\limits_{j=1,a[j]=a[i],a[i]!=P}^{i-1}{s[i]-s[j]\choose 2} j=1,a[j]=a[i],a[i]!=Pi1(2s[i]s[j]),把式子拆开,发现只需要多维护s[]的平方和即可。时间复杂度 O ( n 2 K ) O({n^2\over K}) O(Kn2)

  • Q 1 Q 2 Q 2 Q 1 Q_1Q_2Q_2Q_1 Q1Q2Q2Q1:此时所有二元组 ( x , y ) , a [ x ] = a [ y ] , s i z e [ a [ x ] ] ≤ K (x,y),a[x]=a[y],size[a[x]]\leq K (x,y),a[x]=a[y],size[a[x]]K的总数不超过nK,那么我们将所有二元组 ( x , y ) (x,y) (x,y)看做二维平面上的点,现在就相当于求点 A ( x , y ) , B ( p , q ) , x &lt; p &lt; q &lt; y A(x,y),B(p,q),x&lt;p&lt;q&lt;y A(x,y),B(p,q),x<p<q<y的对数,就变成二维数点问题,将所有点按照纵坐标从小到大扫一遍,树状数组查询即可。时间复杂度 O ( n K log ⁡ n ) O({nK \log n}) O(nKlogn)

总复杂度为 O ( n 2 K + n K log ⁡ n ) O({n^2\over K}+nK\log n) O(Kn2+nKlogn),容易发现当 K = n log ⁡ n K=\sqrt{n\over \log n} K=lognn 时总复杂度最小,为 O ( n n log ⁡ n ) O(n\sqrt{n\log n}) O(nnlogn )

Code

#pragma GCC optimize(2)
#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 676676
#define mo 19260817
#define LL long long
using namespace std;
int n,a[N],d[N],n1,num;
vector<int> pt[N];
LL ans,c1[N],c2[N],cnt[N],le[N],ny2,c[N];
struct node
{
	int x,y;
	friend bool operator <(node x,node y) 
	{
		return x.y<y.y;
	}
}pr[N*20];
int lowbit(int k)
{
	return k&(-k);
}
LL get(int k)
{
	LL s=0;
	while(k) s+=c[k],k-=lowbit(k);
	return s%mo;
}
void ins(int k)
{
	while(k<=n) c[k]++,k+=lowbit(k);	
}
int main()
{
	cin>>n;
	fo(i,1,n) 
	{
		scanf("%d",&a[i]),cnt[i]=++le[a[i]],pt[a[i]].push_back(i);
		if(le[a[i]]==1) d[++d[0]]=a[i];
	}
	ans=0;
	LL v=0;
	fo(i,1,n)
	{
		v=(v-(cnt[i]-1)+mo)%mo;
		ans=(ans+(v-(cnt[i]-1)*(le[a[i]]-cnt[i])%mo+mo)%mo*(cnt[i]-1)%mo+mo)%mo;
		v=(v+(le[a[i]]-cnt[i])+mo)%mo;
	}
	n1=sqrt(n/log2(n)*3);
	ny2=(mo+1)/2;
	num=0;
	fo(i,1,d[0])
	{
		int cl=d[i];
		if(le[cl]>n1)
		{
			memset(c1,0,sizeof(c1));
			memset(c2,0,sizeof(c2));
			LL v=0;
			fo(j,1,n)
			{
				if(a[j]==cl) v++;
				else
				{
					ans=(ans-(le[cl]-v)*c1[a[j]]%mo+mo)%mo;
					if(le[a[j]]<=n1)
					{
						LL vs=((c2[a[j]]+(cnt[j]-1)*v%mo*(v-1)%mo+c1[a[j]]+mo)%mo*ny2-c1[a[j]]*v%mo+mo)%mo;
						ans=(ans-vs+mo)%mo;
					}
					c1[a[j]]=(c1[a[j]]+v)%mo;
					c2[a[j]]=(c2[a[j]]+v*v)%mo;
				}
			}
		}
	}
	fo(i,1,n)
	{
		if(le[a[i]]<=n1)
		{
			fo(u,0,le[a[i]]-1)
			{
				int p=pt[a[i]][u];
				if(p==i) break;
				ans=(ans+(cnt[p]-1)*(le[a[i]]-cnt[i])%mo);
				pr[++num]=(node){p,i};
			}
		}
	}
	int j=1,sm=0;
	fo(i,1,n)
	{
		int p=j;
		while(j<=num&&pr[j].y<=i) ans=(ans-(sm-get(pr[j].x))%mo+mo)%mo,j++;
		fo(k,p,j-1) ins(pr[k].x),sm=(sm+1)%mo;
	}
	printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值