1. 程式人生 > 實用技巧 >【洛谷P4867】Gty的二逼妹子序列

【洛谷P4867】Gty的二逼妹子序列

題目

題目連結:https://www.luogu.com.cn/problem/P4867

Autumn和Bakser又在研究Gty的妹子序列了!但他們遇到了一個難題。

對於一段妹子們,他們想讓你幫忙求出這之內美麗度\(\in [a,b]\)的妹子的美麗度的種類數。

為了方便,我們規定妹子們的美麗度全都在\([1,n]\)中。
給定一個長度為\(n(1 \le n \le 100000)\)的正整數序列\(s(1 \le si \le n)\),對於\(m(1 \le m \le 1000000)\)次詢問l,r,a,b,每次輸出\(s_l \cdots s_r\)中,權值\(\in [a,b]\)的權值的種類數。

思路

如果沒有 \(a,b\) 的約束,隨便搞一個莫隊就可以了。
但是有 \(a,b\) 的限制之後,我們統計答案的時候如果暴力列舉 \([a,b]\) 的數值複雜度顯然是 \(O(nm)\),如果用樹狀陣列求區間和的話,莫隊的端點每移動一次都會多出 \(O(\log n)\) 的複雜度,總複雜度是 \(O(n^{\frac{3}{2}}\log n)\),不可接受。
發現統計答案只有 \(O(m)\) 次,所以我們可以考慮一個修改 \(O(1)\),查詢 \(O(\sqrt{n})\) 的做法。值域分塊一下,每次 \([a,b]\) 就轉換為了左右的零散區間和中間的完整區間。
時間複雜度 \(O(m\sqrt{n})\)

程式碼

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

const int N=100010,M=1000010;
int n,m,T,ans[M],a[N],cnt[N],sum[N],bel[N];

struct node
{
	int l,r,a,b,id;
}ask[M];

bool cmp(node x,node y)
{
	return bel[x.l]==bel[y.l] ? x.r<y.r : bel[x.l]<bel[y.l];
}

void add(int x)
{
	if (!cnt[x]) sum[bel[x]]++;
	cnt[x]++;
}

void del(int x)
{
	cnt[x]--;
	if (!cnt[x]) sum[bel[x]]--;
}

int calc(int l,int r)
{
	int s=0;
	for (int i=l;i<=r;i++)
		s+=(cnt[i]>0);
	return s;
}

int main()
{
	scanf("%d%d",&n,&m);
	T=sqrt(n)+1;
	for (int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		bel[i]=(i-1)/T+1;
	}
	for (int i=1;i<=m;i++)
	{
		scanf("%d%d%d%d",&ask[i].l,&ask[i].r,&ask[i].a,&ask[i].b);
		ask[i].id=i;
	}
	sort(ask+1,ask+1+m,cmp);
	for (int i=1,l=1,r=0;i<=m;i++)
	{
		for (;l>ask[i].l;l--) add(a[l-1]);
		for (;r>ask[i].r;r--) del(a[r]);
		for (;r<ask[i].r;r++) add(a[r+1]);
		for (;l<ask[i].l;l++) del(a[l]);
		int p=(ask[i].a-1)/T+1,q=(ask[i].b-1)/T+1,id=ask[i].id;
		if (p==q) ans[id]=calc(ask[i].a,ask[i].b);
		else
		{
			ans[id]=calc(ask[i].a,T*p)+calc(T*(q-1)+1,ask[i].b);
			for (int j=p+1;j<q;j++)
				ans[id]+=sum[j];
		}
	}
	for (int i=1;i<=m;i++)
		printf("%d\n",ans[i]);
	return 0;
}