loj2254. 「SNOI2017」一个简单的询问

这里用的是分块的做法。

首先可以差分一下,把左区间都换为1,对一个固定的x
g e t ( l 1 , r 1 ) ∗ g e t ( l 2 , r 2 ) = ( g e t ( 1 , r 1 ) − g e t ( 1 , l 1 − 1 ) ) ∗ ( g e t ( 1 , r 2 ) − g e t ( 1 , l 2 − 1 ) ) = g e t ( 1 , r 1 ) ∗ g e t ( 1 , r 2 ) + g e t ( 1 , l 1 − 1 ) ∗ g e t ( 1 , l 2 − 1 ) − g e t ( 1 , r 1 ) ∗ g e t ( 1 , l 2 − 1 ) − g e t ( 1 , r 2 ) ∗ g e t ( 1 , l 1 − 1 ) get(l1,r1)*get(l2,r2)\\ =(get(1,r1)-get(1,l1-1))*(get(1,r2)-get(1,l2-1))\\ =get(1,r1)*get(1,r2)+get(1,l1-1)*get(1,l2-1)-get(1,r1)*get(1,l2-1)-get(1,r2)*get(1,l1-1) get(l1,r1)get(l2,r2)=get(1,r1)get(1,l11))(get(1,r2)get(1,l21))=get(1,r1)get(1,r2)+get(1,l11)get(1,l21)get(1,r1)get(1,l21)get(1,r2)get(1,l11)
上面能拆开是因为get是线性的。

然后就要处理get(1,p)*get(1,q),分块一下,每块大小N=sqrt(n),在N,2N,3N,……这些位置预处理出区间[1,k*N]中每个数出现的次数。同时可以预处理出get(1,k1*N)*(get1,k2*N)的值。

然后对get(1,p)*get(1,q),找到p,q包含的最大已处理的块(对p,找到最大k*N<=p),剩余部分暴力跑。

#include <bits/stdc++.h>
using namespace std;

#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef long long ll;
typedef double db;
typedef vector<int> VI;
typedef pair<int,int> PII;
typedef pair<long long,int> PLI;
const ll mod=1000000007;
//mt19937_64 mrand(random_device{}()); 
//int rnd(int x) { return mrand() % x;}
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;}

int n,_,N;
int a[50066];
int A[250][50066],B[250][50066];
ll w[250][250];

void init()
{
	for(int id=1,r=N;r<=n;id++,r=r+N)
	{
		for(int i=1;i<=r;i++)
		{
			A[id][a[i]]++;
			B[id][a[i]]++;
		}
		ll W=0;
		for(int i=1;i<=n;i++)W+=1ll*A[id][i]*A[id][i];
		w[id][id]=W;
		for(int i=r+1;i<=n;i++)
		{
			W+=A[id][a[i]];
			if(i%N==0)
			{
				w[id][i/N]=W;
			}
		}
	}
	// for(int i=1;i<=2;i++)
	// {
	// 	for(int j=i;j<=2;j++)
	// 	{
	// 		printf("%lld ",w[i][j]);
	// 	}puts("");
	// }
}

ll solve(int p,int q)
{
	if(p>q)swap(p,q);
	if(p==0)return 0;
	ll s=(p)/N,t=(q)/N;
	ll ret=w[s][t];
	for(int i=s*N+1;i<=p;i++)
	{
		A[s][a[i]]++;
		ret+=B[t][a[i]];
	}
	for(int i=t*N+1;i<=q;i++)
	{
		ret+=A[s][a[i]];
	}
	for(int i=s*N+1;i<=p;i++)
	{
		A[s][a[i]]--;
	}
	//printf("___%d %d %lld\n",p,q,ret);
	return ret;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	N=sqrt(n);
	init();
	scanf("%d",&_);
	while(_--)
	{
		int l1,r1,l2,r2;
		scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
		l1--;l2--;
		ll ans=solve(r1,r2)+solve(l1,l2)-solve(l1,r2)-solve(l2,r1);
		printf("%lld\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值