普通莫队题集


SPOJ D-query

相同题目:

LightOJ - 1188 Fast Queries

题意:

求区间内不同数字的数量

分析:

最简单题,开一个cnt数组记录数字出现的数量就行了

code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int maxm=3e6+5;
struct Node{
    int l,r;
    int id;
}q[maxm];
int a[maxm],cnt[maxm],res[maxm];
int block;
int ans;
bool cmp(Node a,Node b){
    if(a.l/block==b.l/block)return a.r<b.r;
    return a.l<b.l;
}
void add(int x){
    cnt[a[x]]++;
    if(cnt[a[x]]==1)ans++;
}
void del(int x){
    cnt[a[x]]--;
    if(cnt[a[x]]==0)ans--;
}
int main(){
    int n;
    scanf("%d",&n);
    block=sqrt(1.0*n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int m;
    scanf("%d",&m);
    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);
    ans=0;
    int l=1,r=0;
    for(int i=1;i<=m;i++){
        int ql=q[i].l,qr=q[i].r;
        while(l<ql)del(l++);;
        while(l>ql)add(--l);
        while(r<qr)add(++r);
        while(r>qr)del(r--);
        res[q[i].id]=ans;
    }
    for(int i=1;i<=m;i++){
        printf("%d\n",res[i]);
    }
    return 0;
}

P2709 小B的询问

分析:

更新的时候可以直接减去旧的a2然后a++再加上a2
也可以直接增加或者减少a2关于(a+1)2或(a-1)2的差值(这个还要算,不够无脑)

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=5e4+5;
struct Node{
    int l,r,id,b;
    bool operator < (const Node& x)const{
        if(b==x.b)return r<x.r;
        return b<x.b;
    }
}q[maxm];
int cmp(Node a,Node b){
    if(a.b==b.b)return a.r<b.r;
    return a.b<b.b;
}
int a[maxm];
int res[maxm];
int sum[maxm];
int block;
int ans;
void add(int x){
    ans-=sum[a[x]]*sum[a[x]];
    sum[a[x]]++;
    ans+=sum[a[x]]*sum[a[x]];
}
void del(int x){
    ans-=sum[a[x]]*sum[a[x]];
    sum[a[x]]--;
    ans+=sum[a[x]]*sum[a[x]];
}
int main(){
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    block=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;
        q[i].b=(q[i].l-1)/block+1;
    }
    sort(q+1,q+1+m,cmp);
    ans=0;
    int l=1,r=0;
    for(int i=1;i<=m;i++){
        int ql=q[i].l;
        int qr=q[i].r;
        while(l<ql)del(l++);
        while(l>ql)add(--l);
        while(r<qr)add(++r);
        while(r>qr)del(r--);
        res[q[i].id]=ans;
    }
    for(int i=1;i<=m;i++){
        cout<<res[i]<<endl;
    }
    return 0;
}


NBUT-1457 Sona

分析:

和上一题差不多,不过需要统计的数字大到了1e9,不能直接开这么大数组
但是n最大只有1e5,所以可以把数据离散化到1e5以内
另外这题很坑,sqrt函数里面放int会编译失败,必须放double,而且不提示在哪里错(不要问我最后怎么找到的,心态爆炸)。


CodeForces - 220B Little Elephant and Array

分析:

离散化+判断,其实和前面的也差不多

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxm=1e5+5;
struct Node{
    int l,r,id,b;
    bool operator < (const Node a){
        if(b==a.b)return r<a.r;
        return l<a.l;
    }
}q[maxm];
int res[maxm];
int a[maxm];
int xx[maxm];
int sum[maxm];
int ans;
void add(int x){
    if(sum[a[x]]==xx[a[x]]){
        ans--;
    }
    sum[a[x]]++;
    if(sum[a[x]]==xx[a[x]]){
        ans++;
    }
}
void del(int x){
    if(sum[a[x]]==xx[a[x]]){
        ans--;
    }
    sum[a[x]]--;
    if(sum[a[x]]==xx[a[x]]){
        ans++;
    }
}
int main(){
    ios::sync_with_stdio(0);
    int n,m;
    cin>>n>>m;
    int block=sqrt(n*1.0);
    int cnt=0;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        xx[cnt++]=a[i];
    }
    sort(xx,xx+cnt);
    cnt=unique(xx,xx+cnt)-xx;
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(xx,xx+cnt,a[i])-xx;
    }
    for(int i=1;i<=m;i++){
        cin>>q[i].l>>q[i].r;
        q[i].id=i;
        q[i].b=q[i].l/block;
    }
    sort(q+1,q+1+m);
    int l=q[1].l,r=l-1;
    for(int i=1;i<=m;i++){
        int ql=q[i].l,qr=q[i].r;
        while(l>ql)add(--l);
        while(l<ql)del(l++);
        while(r>qr)del(r--);
        while(r<qr)add(++r);
        res[q[i].id]=ans;
    }
    for(int i=1;i<=m;i++){
        cout<<res[i]<<endl;
    }
    return 0;
}


CodeForces-86D Powerful array

题意:

求区间内:每种数的个数的平方乘上这个数(有点绕)的和(晕)
例如:
假设区间内有一个数x,他的个数为k,则ans+=k2x
每种数都加一遍

分析:

其实和开始求平方的差不多

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=1e6+5;
struct Node{
    int l,r,id,b;
    bool operator <(const Node &aa)const {
        if(b==aa.b)return r<aa.r;
        return l<aa.l;
    }
}q[maxm];
ll ans;
int a[maxm],cnt[maxm];
ll res[maxm];
void add(int x){
    ans-=(ll)cnt[a[x]]*cnt[a[x]]*a[x];
    cnt[a[x]]++;
    ans+=(ll)cnt[a[x]]*cnt[a[x]]*a[x];
}
void del(int x){
    ans-=(ll)cnt[a[x]]*cnt[a[x]]*a[x];
    cnt[a[x]]--;
    ans+=(ll)cnt[a[x]]*cnt[a[x]]*a[x];
}
int main(){
    int n,m,block;
    scanf("%d%d",&n,&m);
    block=sqrt(1.0*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;
        q[i].b=q[i].l/block;
    }
    sort(q+1,q+1+m);
    ans=0;
    int l=q[1].l,r=q[1].l-1;
    for(ll i=1;i<=m;i++){
        int ql=q[i].l,qr=q[i].r;
        while(l<ql)del(l++);
        while(l>ql)add(--l);
        while(r<qr)add(++r);
        while(r>qr)del(r--);
        res[q[i].id]=ans;
    }
    for(int i=1;i<=m;i++){
        printf("%I64d\n",res[i]);
    }
    return 0;
}

P1494 [国家集训队]小Z的袜子

分析:

假设区间[L,R]内数量的几种袜子的数量为a,b
则概率为 (a*(a-1)/2+b*(b-1)/2) / ((R-L+1)*(R-L)/2)

(a,b如果等于1答案没有影响,因为乘上a-1就变成0了)
分子为取到两件相同的可能方案
分母为随机两件的所有可能方案
上下同时乘2去掉分母2之后变成(a*(a-1)+b*(b-1)) / ((R-L+1)*(R-L))

分子去括号变成(a2+b2-(a+b),而a+b又等于(R-L+1)

所以最后式子为(a2+b2-(R-L+1))/((R-L+1)*(R-L))

(R-L+1)可以直接算出来的,剩下的就只有和之前题目一样的平方了

记得特判0
约分的话再写一个gcd函数就行了

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=1e5+5;
struct Node{
    int l,r,id,b;
    bool operator <(const Node &aa)const {
        if(b==aa.b)return r<aa.r;
        return l<aa.l;
    }
}q[maxm];
int n,m,block;
ll ans;
int a[maxm],cnt[maxm];
ll x[maxm],y[maxm];
ll gcd(ll a,ll b){
    return b==0?a:gcd(b,a%b);
}
void add(int x){
    ans-=cnt[a[x]]*cnt[a[x]];
    cnt[a[x]]++;
    ans+=cnt[a[x]]*cnt[a[x]];
}
void del(int x){
    ans-=cnt[a[x]]*cnt[a[x]];
    cnt[a[x]]--;
    ans+=cnt[a[x]]*cnt[a[x]];
}
int main(){
    scanf("%d%d",&n,&m);
    block=sqrt(1.0*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;
        q[i].b=q[i].l/block;
    }
    sort(q+1,q+1+m);
    ans=0;
    int l=1,r=0;
    for(int i=1;i<=m;i++){
        int ql=q[i].l,qr=q[i].r;
        while(l<ql)del(l++);
        while(l>ql)add(--l);
        while(r<qr)add(++r);
        while(r>qr)del(r--);
        int id=q[i].id;
        if(ans==0){
            x[id]=0,y[id]=1;
            continue;
        }
        ll len=q[i].r-q[i].l+1;
        x[id]=ans-len;
        y[id]=len*(len-1);
        ll d=gcd(x[id],y[id]);
        x[id]/=d;
        y[id]/=d;
    }
    for(int i=1;i<=m;i++){
        printf("%lld/%lld\n",x[i],y[i]);
    }
    return 0;
}

P3709 大爷的字符串题

题意:

求区间内数字最多的数的个数
输出它的相反数
(原题题面太恐怖了根本看不懂,我怀疑出题人就是想恶心人。。)

分析:

开一个数组sum[i]记录数字i出现的个数
再开一个数组num[i]记录出现i次的数字的个数
能想到这两个数组就好做了
这题数据很大,需要离散化

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=2e5+5;
struct Node{
    int l,r,id,b;
}q[maxm];
int a[maxm];
int res[maxm];
int temp[maxm];
int sum[maxm];//数字i出现了几次
int num[maxm];//有多少个出现i次的数字
int block;
int ans;
bool cmp(Node a,Node b){
    if(a.b==b.b)return a.r<b.r;
    return a.l<b.l;
}
void add(int x){
    num[sum[a[x]]]--;
    sum[a[x]]++;
    num[sum[a[x]]]++;
    ans=max(ans,sum[a[x]]);//更新最大值
}
void del(int x){
    if(num[sum[a[x]]]==1&&ans==sum[a[x]])ans--;
    num[sum[a[x]]]--;
    sum[a[x]]--;
    num[sum[a[x]]]++;
}
int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    block=sqrt(n*1.0);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        temp[i]=a[i];
    }
    sort(temp+1,temp+1+m);
    int cnt=unique(temp+1,temp+1+m)-temp;
    for(int i=1;i<=n;i++){
        a[i]=lower_bound(temp+1,temp+cnt,a[i])-temp;
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].id=i;
        q[i].b=(q[i].l-1)/block+1;
    }
    sort(q+1,q+1+m,cmp);
    int l=1,r=0;
    ans=1;
    num[1]=inf;
    for(int i=1;i<=m;i++){
        int ql=q[i].l,qr=q[i].r;
        while(l<ql)del(l++);
        while(l>ql)add(--l);
        while(r<qr)add(++r);
        while(r>qr)del(r--);
        res[q[i].id]=ans;
    }
    for(int i=1;i<=m;i++){
        printf("%d\n",-res[i]);
    }
    return 0;
}

CodeForces - 617E XOR and Favorite Number

题意:

给n,m,k,
一个长度为n的数列,m个询问,
每个询问给一个区间,问区间内有多少个数对(i,j),满足区间[i,j]异或和(a[i]^a[i+1]…a[j])=k

分析:

题目问区间异或和
显然要用到异或前缀和,区间异或和就是sum[r]^sum[l-1]
这样之后题目就变成求数对(i,j),满足sum[j]^sum[i-1]=k
根据异或的性质我们可以把这个式子变成:
sum[i-1]^k=sum[j],
或者sum[j]^l=sum[i-1]
假设更新的位置上的数为x,其实就是找区间内sum[x]^k的个数(看不懂就看代码,打字说不太明白)
开一个数组记录数字出现的个数就行了(统计个数是最简单的操作)

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int maxm=2e6+5;
struct Node{
    int l,r,id,b;
    bool operator < (const Node &a)const{
        if(b==a.b)return r<a.r;
        return l<a.l;
    }
}q[maxm];
ll res[maxm];
ll ans;
int a[maxm],sum[maxm];
int n,m,k;
void add(int x){
    ans+=sum[a[x]^k];
    sum[a[x]]++;
}
void del(int x){
    sum[a[x]]--;
    ans-=sum[a[x]^k];
}
int main(){
    scanf("%d%d%d",&n,&m,&k);
    int block=sqrt(1.0*n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        a[i]^=a[i-1];
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d",&q[i].l,&q[i].r);
        q[i].l--;
        q[i].id=i;
        q[i].b=q[i].l/block;
    }
    sort(q+1,q+1+m);
    int l=q[1].l,r=l-1;
    for(int i=1;i<=m;i++){
        int ql=q[i].l,qr=q[i].r;
        while(l<ql)del(l++);
        while(l>ql)add(--l);
        while(r<qr)add(++r);
        while(r>qr)del(r--);
        res[q[i].id]=ans;
    }
    for(int i=1;i<=m;i++){
        printf("%lld\n",res[i]);
    }
    return 0;
}

CodeForces - 375D.Tree and Queries

题意:

n个数,col[i]对应第i个数的颜色,并给你他们之间的树形关系(以1为根),有m次询问,每次给出vi,ki,要求找出以点vi为根的子树上出现超过ki次的颜色数。

思路:

子树问题利用dfs序转化为序列区间问题,然后就可以上莫队了
开个数组num[x]表示出现超过x次的颜色数

code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxm=2e5+5;
struct Node{
    int l,r,id,b,k;
    bool operator <(const Node &a)const{
        if(b==a.b)return r<a.r;
        return l<a.l;
    }
}q[maxm];
int a[maxm],res[maxm],col[maxm];
int cnt[maxm];
int num[maxm];
vector<int>g[maxm];
int L[maxm],R[maxm];
int n,m;
void dfs(int x,int &id){
    L[x]=++id;
    a[L[x]]=col[x];
    for(int i=0;i<(int)g[x].size();i++){
        int v=g[x][i];
        if(!L[v])dfs(v,id);
    }
    R[x]=id;
}
void add(int x){
    cnt[a[x]]++;
    num[cnt[a[x]]]++;
}
void del(int x){
    num[cnt[a[x]]]--;
    cnt[a[x]]--;
}
int main(){
    cin>>n>>m;
    int block=sqrt(1.0*n);
    for(int i=1;i<=n;i++){
        cin>>col[i];
    }
    for(int i=1;i<=n-1;i++){
        int a,b;
        cin>>a>>b;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    int id=0;
    dfs(1,id);
    for(int i=1;i<=m;i++){
        int vj,kj;
        cin>>vj>>kj;
        q[i].l=L[vj];
        q[i].r=R[vj];
        q[i].k=kj;
        q[i].id=i;
        q[i].b=q[i].l/block;
    }
    sort(q+1,q+1+m);
    int l=q[1].l,r=l-1;
    for(int i=1;i<=m;i++){
        int ql=q[i].l,qr=q[i].r;
        while(l<ql)del(l++);
        while(l>ql)add(--l);
        while(r<qr)add(++r);
        while(r>qr)del(r--);
        res[q[i].id]=num[q[i].k];
    }
    for(int i=1;i<=m;i++){
        cout<<res[i]<<endl;
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值