例题
牛客暑期训练营 J
题意描述:
输入一串整数(n<=1e5),对于每个询问(i,j),输出a[1]…a[i]以及a[j]…a[n]里数字的种类数,询问一共q个(<=n)
思路分析:
暴力解法,即对于每个询问均通过遍历寻找答案,会令复杂度高达O(n^2),考虑对其进行优化。
我们知道对于询问(i,j),去掉i对结果造成的影响加上j对结果造成的影响,在O(1)内就变为了询问(i+1,j+1),在这种情况下的拖慢程序的,是询问的无序性,如果第一次询问的左下标为1,第二次就变为了n-1,就会让该算法无法发挥其优势。故考虑对询问进行排序。
排序采用分块排序的原则,按照左下标的位置进行分块,左下标相同则对右下标进行升序排列,可以证明这样一来该算法的整体复杂度为O(n*unit+n*n/unit),根据基本不等式,令unit块的大小为n^0.5式取到最小值n^1.5,这里给出做出完整证明的一篇文章 https://www.cnblogs.com/Paul-Guderian/p/6933799.html 。
接下来是本题代码,很遗憾直到最后依然有re没有调出来,测试通过率50%,不过看到有一段也用了这个算法的成功ac,代码思路基本相同,先只当过了吧……
//J
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
int ans[100001],a[100001],cnt[100001];
int ln,rn;
const int block=520;
int n,q,sum;
struct query
{
int id;
int l,r;
bool operator<(const query& q)
{
return (l/block)==(q.l/block)?r<q.r:l<q.l;
}
} qaq[100001];
void add(int x)
{
if(!cnt[x])
sum++;
cnt[x]++;
}
void del(int x)
{
cnt[x]--;
if(!cnt[x])
sum--;
}
int main()
{
while(cin>>n>>q)
{
memset(cnt,0,sizeof(cnt));
for(int i=1; i<=n; i++)
cin>>a[i];
for(int i=1; i<=q; i++)
{
cin>>qaq[i].l>>qaq[i].r;
qaq[i].id=i;
}
sort(qaq+1,qaq+1+q);
sum=0;
ln=qaq[1].l;
rn=qaq[1].r;
for(int i=1;i<=ln;i++)
add(a[i]);
for(int i=rn;i<=n;i++)
add(a[i]);
for(int i=1; i<=q; i++)
{
while(qaq[i].l>ln)
add(a[++ln]);
while(qaq[i].l<ln)
del(a[ln--]);
while(qaq[i].r>rn)
del(a[rn++]);
while(qaq[i].r<rn)
add(a[--ln]);
ans[qaq[i].id]=sum;
}
for(int i=1; i<=q; i++)
cout<<ans[i]<<endl;
}
return 0;
}