链接
题意:
小象喜欢和数组玩。现在有一个数组 a a a,含有 n n n 个正整数,记第 i i i 个数为 a i a_i ai 。现在 m m m个询问,每个询问包含两个正整数 l j l_j lj 和 r j r_j rj; ( 1 ⩽ l j ⩽ r j ⩽ n ) (1\leqslant l_j\leqslant r_j\leqslant n) (1⩽lj⩽rj⩽n),小象想知道在 A l j A_{l_j} Alj 到 A r j A_{r_j} Arj 之中有多少个数 x x x,其出现次也为 x x x。
暴力剪枝:
说是暴力剪枝其实也不是真正的暴力,其实是前缀和优化再去剪枝。
首先我们看剪枝:一共
n
n
n个数,要求是区间内,这个数个数等于这个数,那么我们可以知道最多有
n
\sqrt n
n个数符合条件。所以大于n 的数我们直接不用去看,他一定不会满足条件。 小于等于n的数提取出来,记录一下数量,数量大于等于这个数的我们用前缀和维护,然后每次查询我们判断这
n
\sqrt n
n有多少符合条件。
时间复杂度是
O
(
(
q
+
n
)
n
)
O((q+n)\sqrt n)
O((q+n)n)
用int 就行 long long好像会暴内存
int n,q;
int a[maxn],num[maxn],vis[maxn],tree[500][maxn];
void solve()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(a[i]<=n){
num[a[i]]++;
}
}
int cnt=0;
for(int i= 1 ;i<=n;i++)
{
if(num[i]>=i){
vis[++cnt]=i;
for(int j=1;j<=n;j++){
tree[cnt][j]=tree[cnt][j-1]+(a[j]==i);
}
}
}
while(q--){
int l,r;
scanf("%d%d",&l,&r);
int ans=0;
for(int i=1;i<=cnt;i++){
if(tree[i][r]-tree[i][l-1] == vis[i]){
ans++;
}
}
printf("%d\n",ans);
}
}
莫队:
我们利用莫队进行离线的查询。
首先还是像上面那样的剪枝,大于n的数肯定不会有贡献,所以不用管,用莫队是因为,我们对每一段进行处理,但是我们发现这些区间有很多是重复的,那么我们就可以减少对这部分的处理。所以我们对每一段进行排序,然后处理这段,之后下一次处理利用到前一段,将其扩缩成当前这个区间即可。计算答案的条件,如果增加一段,加上这一段中数,之后这个数x的个数变成x那么答案将+1,如果个数变成x+1那么答案将-1,如果需要减掉一段数,之后某个数x的个数变成了x那么答案将+1,如果个数变成x-1那么答案将-1.
时间复杂度
O
(
q
l
o
g
q
+
n
n
)
O(qlog_q+n\sqrt n)
O(qlogq+nn)
这是没有分块的优化:3800ms左右 差点超时 (也可以说是纯暴力了时间还没上面那个优)
int n,m,res;
int a[maxn],ans[maxn],num[maxn];
struct node {
int l,r;
int id;
}q[maxn];
bool cmp(node a,node b){
if(a.l!=b.l) return a.l<b.l;
return a.r<b.r;
}
void Insert(int x){
if(a[x] > n) return ;
++num[a[x]];
if(num[a[x]]==a[x]) ++res;
if(num[a[x]]==a[x]+1) --res;
}
void Delete(int x){
if(a[x]>n) return ;
if(num[a[x]]== a[x]+1) res++;
if(num[a[x]]==a[x]) res--;
num[a[x]]--;
}
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
int x =q[i].l,y=q[i].r,id=q[i].id;
while(r<y) Insert(++r);
while(l>x) Insert(--l);
while(r>y) Delete(r--);
while(l<x) Delete(l++);
ans[id]= res;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
基本的莫队:
int n,m,res,k;
int a[maxn],ans[maxn],num[maxn];
struct node {
int l,r;
int id;
}q[maxn];
bool cmp(node a,node b){
if(a.l/k==b.l/k) return a.r<b.r;
return a.l/k<b.l/k;
}
void Insert(int x){
if(a[x] > n) return ;
++num[a[x]];
if(num[a[x]]==a[x]) ++res;
if(num[a[x]]==a[x]+1) --res;
}
void Delete(int x){
if(a[x]>n) return ;
if(num[a[x]]== a[x]+1) res++;
if(num[a[x]]==a[x]) res--;
num[a[x]]--;
}
void solve(){
scanf("%d%d",&n,&m);
k=sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
int l=1,r=0;
for(int i=1;i<=m;i++){
int x =q[i].l,y=q[i].r,id=q[i].id;
while(r<y) Insert(++r);
while(l>x) Insert(--l);
while(r>y) Delete(r--);
while(l<x) Delete(l++);
ans[id]= res;
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
树状数组:
首先了解下树状数组,可维护区间和,我们这个题是找区间贡献值的。所以可行,只不过需要加工。
怎么加工那?
首先我们树状数组查询答案,是 s[r]-s[l-1],
如果我们有一个数x是这样的
1 2 3 4 5 6 ..... y
x x x x x x ..... x
这样的话我们从最后一个x看,他产生贡献的左端点是y-x+1,因为我们记录的是差那么y-x这个位置上我们为-1即可,然后我们对新生成的数组进行差分,得到y-x位置上为-1,y-x+1位置上为1.说是差分,也可以理解为记录y对应的左端点,然后我们看前一个状态 右端点为y-1那么同样的 y-1-x位置上为-1,y-x位置上为1,我们从 右端点为y-1到y 其实是y-1-x位置上-1变成0,所以需要+1, y-x位置上1变成-1,所以需要-2,y-x+1位置上,从0变成1所以需要+1.
这样构建树状数组即可查询。
vector <pii> q[maxn];
vector <int> g[maxn];
int n,m;
int a[maxn],ans[maxn],tree[maxn];
void update(int pos,int num){
while(pos <= n){
tree[pos]+=num;
pos+=lowbit(pos);
}
}
int query (int pos){
int sum = 0;
while (pos){
sum+=tree[pos];
pos-=lowbit(pos);
}
return sum;
}
void solve(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
int l,r;
scanf("%d%d",&l,&r);
q[r].push_back({l,i});
}
for(int i=1;i<=n;i++){
int x = a[i];
if(x>n) continue;
g[x].push_back(i);
int size = g[x].size();
if(size >=x){
update(g[x][size-x],1);
if(size>=x+1) update(g[x][size-x-1],-2);
if(size>=x+2) update(g[x][size-x-2],1);
}
for(int j=0;j<q[i].size();j++){
pii p=q[i][j];
ans[p.y]=query(i)-query(p.x-1);
}
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}