JZOJ 5951. 【NOIP2018模拟11.04】锋芒毕露

本文探讨了一道关于数轴上带有颜色的点和线段的复杂算法问题。目标是计算不同颜色线段的交叉数量。通过区分AABB和ABBA型线段,采用暴力解法结合优化策略,如树状数组和阈值分类,实现高效求解。文章详细阐述了解决方案的思路和具体实现步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

一条数轴上有n个点,第i个点的坐标为i。
每个点有一个颜色 c o l [ i ] col[i] col[i],任意相同颜色的点都可以用线将他们连起来,线段的颜色和点的颜色相同。
求有多少对线段相交,且他们颜色不同。

题解

直接搞不太好搞。正难则反,用总数减去不合法的即为答案。
这题的正解就是个暴力,但是设个阈值,分类讨论。
分两种情况:
情况①,AABB型。
利用 s 1 [ i ] s_1[i] s1[i]表示在 i i i前面有多少个相同颜色的点。
均摊 O ( 1 ) O(1) O(1)搞一下即可。
时间复杂度: O ( n ) O(n) O(n)
情况②,ABBA型。
令出现次数 > K >K >K的颜色为L,出现次数 ≤ K \leq K K的颜色为P。
又有3种情况, P 1 , P 2 , P 2 , P 1 P_1,P_2,P_2,P_1 P1,P2,P2,P1 ∗ L , L , ∗ *L,L,* L,L, L P P L LPPL LPPL
情况一, P 1 , P 2 , P 2 , P 1 P_1,P_2,P_2,P_1 P1,P2,P2,P1,用树状数组求。
对于每个 P 1 ( i , j ) P_1(i,j) P1(i,j),统计有多少对 P 2 ( l , r ) P_2(l,r) P2(l,r),满足 i &lt; l &lt; r &lt; j i&lt;l&lt;r&lt;j i<l<r<j
对于每个r,将 O ( k ) O(k) O(k)个l加入树状数组,然后求个前缀和即可。
求完之后,将i加入树状数组。
设置阈值之后,可知道L的种数不是很多,因此一个个枚举即可。
时间复杂度: O ( n k   l o g   n ) O(nk\ log\ n) O(nk log n)
情况二, ∗ , L , L , ∗ *,L,L,* ,L,L,
对于每个 L L L,枚举每个 ∗ * ,记第i个 ∗ * 前面出现了 a [ i ] a[i] a[i]个L。
则每个 ( i , j ) (i,j) (i,j),对答案的贡献为 C a i − a j 2 C_{a_i-a_j}^2 Caiaj2
维护 ∑ j = 1 i − 1 a [ j ] 2 \sum_{j=1}^{i-1}a[j]^2 j=1i1a[j]2 ∑ j = 1 i − 1 a [ j ] \sum_{j=1}^{i-1}a[j] j=1i1a[j]即可。
时间复杂度: O ( n 2 k ) O(\frac{n^2}{k}) O(kn2)
情况三, L P P L LPPL LPPL
对于每个 L L L,枚举每个P。只要知道每个P前面有多少个L,后面有多少个L即可。
时间复杂度: O ( n 2 k ) O(\frac{n^2}{k}) O(kn2)
基于暴力扫,只不过不同情况不同方法罢了。

要说的

都到这个时候了,练练码力即可。

代码

#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<cstdio>
#define N 666670
#define mo 19260817
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,k,l,n,m,K;
int ans,sum,temp;
int gs[N],a[N],f1[N];
int pm[N];
int Sig_B[N],Bi,Ci;
int Sig_A[N],Sig_A2[N],Gs[N],Ai;
int tr[N];
int la[N],bw[N],hd[N];
int Calc[N];
bool bz[N];
int read(){
	int fh=0,rs=0;char ch=0;
	while((ch<'0'||ch>'9')&&(ch^'-'))ch=getchar();
	if(ch=='-')fh=1,ch=getchar();
	while(ch>='0'&&ch<='9')rs=(rs<<3)+(rs<<1)+(ch^'0'),ch=getchar();
	return fh?-rs:rs;
}
int lowbit(int x){return x&(-x);}
void add(int x,int num){
	for(;x<=n;x=x+lowbit(x))tr[x]=tr[x]+num;
}
int qry(int x){
	int rs=0;
	for(;x;x=x-lowbit(x))rs=rs+tr[x];
	return rs;
}
int main(){
	n=read();
    fo(i,1,n){
    	a[i]=read();
    	if(!hd[a[i]])hd[a[i]]=i;
    	la[i]=bw[a[i]];
    	bw[a[i]]=i;
    	gs[a[i]]++;
	}
	fo(i,1,n)if(gs[i]){
		f1[i]=1ll*gs[i]*(gs[i]-1)%mo;
		f1[i]=1ll*f1[i]*9630409%mo;
		sum=(sum+f1[i])%mo;
	}
	fo(i,1,n)if(gs[i]){
		ans=(ans+1ll*f1[i]*(sum-f1[i]+mo)%mo)%mo;
	}
	ans=(1ll*ans*9630409)%mo;//总数 
	//AABB
	pm[0]=0;
	fo(i,2,n){
		Calc[i]=(Calc[i-1]+i-1)%mo;
	}
	fo(i,1,n){
		pm[i]=pm[la[i]]+1;
		sum=(sum-(gs[a[i]]-pm[i])+mo)%mo;
		temp=(sum-Calc[gs[a[i]]-pm[i]]+mo)%mo;
		ans=(ans-1ll*temp*(pm[i]-1)%mo+mo)%mo;
	}
	//ABBA
	K=sqrt(n/log2(n));
	fo(i,1,n)if(gs[i]>=K)bz[i]=1;
	//1 LPPL
	fo(i,1,n)if(bz[i]){
		fo(j,1,n)Sig_B[j]=0;
		Bi=0;
		fo(j,1,n){
			k=a[j];
			if(k==i)Bi++;
			else
			if(!bz[a[j]]){
				Ci=gs[i]-Bi;
			    ans=(ans-1ll*Sig_B[k]*Ci%mo+mo)%mo;
			    Sig_B[k]+=Bi;
			}
		}
	}
	//2 *LL*
	fo(i,1,n)if(bz[i]){
		fo(j,1,n)Sig_A[j]=Sig_A2[j]=Gs[j]=0;
		temp=0;
		fo(j,1,n){
			if(a[j]==i)Ai++;
			else{
				k=a[j];
				temp=(temp+1ll*Gs[k]*(1ll*Ai*Ai)%mo)%mo;
				temp=(temp+Sig_A2[k]+Sig_A[k])%mo;
				temp=(temp-2ll*Ai*Sig_A[k]%mo)%mo;
				temp=(temp-1ll*Gs[k]*Ai%mo)%mo;
				Sig_A[k]=(Sig_A[k]+Ai)%mo;
				Sig_A2[k]=(Sig_A2[k]+1ll*Ai*Ai%mo)%mo;
				Gs[k]++;
			}
		}
		ans=(ans-1ll*temp*9630409%mo+mo)%mo;
	}
	//3 PPPP
	fo(i,1,n)if(gs[a[i]]&&!bz[a[i]]){
		for(j=la[i];j;j=la[j]){
			temp=(qry(i-1)-qry(j)+mo)%mo;
			temp=(temp-Calc[pm[i]-pm[j]]+mo)%mo;
			ans=(ans-temp+mo)%mo;
			add(j,1);
		}
	}
	printf("%d",ans);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值