莫队练习7.22

小B的询问

题目链接:https://www.luogu.com.cn/problem/P2709

大意

给定一个区间,每次询问区间[l,r]中不同元素出现次数的平方和。

思路

静态区间可使用莫队算法离线操作

将每个查询区间按分块的区域排序,定义一个双指针,通过add和sub操作维护输出值。

莫队板子题

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=50007;
ll a[N];
ll ans[N];
ll pos[N];
ll cnt[N];
struct node
{
    ll l,r,k;
}q[N];
ll res;
bool cmp(node &f,node &s)
{
    return pos[f.l]==pos[s.l]?f.r<s.r:pos[f.l]<pos[s.l];
}
void Add(ll p)
{
	cnt[a[p]]++;
	res+=cnt[a[p]]*cnt[a[p]]-(cnt[a[p]]-1)*(cnt[a[p]]-1);
}
void Sub(ll p)
{
	res-=cnt[a[p]]*cnt[a[p]]-(cnt[a[p]]-1)*(cnt[a[p]]-1);
	cnt[a[p]]--;
}
int main()
{
    ll n,m,k;
    cin>>n>>m>>k;
    //分块
    ll siz=sqrt(n);
    for(ll i=1;i<=n;i++)
    {
        cin>>a[i];
        pos[i]=i/siz;
    }
    for(ll i=0;i<m;i++)
    {
        cin>>q[i].l>>q[i].r;
        q[i].k=i;
    }
    sort(q,q+m,cmp);
    ll l=1,r=0;
    for(ll i=0;i<m;i++)
    {
        while(q[i].l<l)
            Add(--l);
        while(q[i].r>r)
            Add(++r);
        while(q[i].l>l) 
            Sub(l++);
        while(q[i].r<r) 
            Sub(r--);
        ans[q[i].k]=res;
    }
    for(ll i=0;i<m;i++)
        cout<<ans[i]<<endl;
    return 0;
}

zoto

题目链接:https://acm.hdu.edu.cn/showproblem.php?pid=6959

大意

给定一个数组,求区间[x0,x1]中值域在[y0,y1]之间的不同元素个数

思路

主席树也不知道假不假,标程是莫队那就莫队吧

具体模板和上面的题类似,具体更改add和sub函数

建一个树状数组用来维护值域[1,n]中不同元素的个数(前缀和),注意题目中值域可能出现0,为了树状数组的可使用我们可以将所有值都强制+1.

怎么保证存到树状数组内的是不同元素个数呢,我们可以用数组记录当前出现元素的个数,当add操作中元素个数为1时存入当前位置1,当sub操作中元素个数为0时存入当前位置-1.(就是这个当前位置强制+1)

莫队大概也就是个暴力(贪心)?一般静态情况修改add和sub操作就够了,非静态也没写过,碰到再学。

CODE

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=100007;
int a[N];
int ans[N];
int pos[N];
int cnt[N];
int v[N];
int sum(int i){
    int res=0;
    for(;i>0;i-=i&-i)   res+=v[i];
    return res;
}
void add(int i,int x){
    for(;i<N;i+=i&-i) v[i]+=x;
}
struct node
{
    int l,r,ql,qr,k;
}q[N];
bool cmp(node f,node s)
{
    return pos[f.l]==pos[s.l]?(pos[f.l]&1?f.r<s.r:f.r>s.r):pos[f.l]<pos[s.l];
}
void Add(int p)
{
    cnt[a[p]]++;
    if(cnt[a[p]]==1)
        add(a[p]+1,1);
}
void Sub(int p)
{
    cnt[a[p]]--;
    if(cnt[a[p]]==0)
    {
        add(a[p]+1,-1);
    }
}
int main()
{
    //ios::sync_with_stdio(0);
    int t;cin>>t;
    while(t--)
    {
        memset(v,0,sizeof(v));
        memset(cnt,0,sizeof(cnt));
        memset(ans,0,sizeof(ans));
        memset(pos,0,sizeof(pos));
        int n,m;
        scanf("%d %d",&n,&m);
        //分块
        int siz=sqrt(n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            pos[i]=i/siz;
        }
        for(int i=0;i<m;i++)
        {
            scanf("%d %d %d %d",&q[i].l,&q[i].ql,&q[i].r,&q[i].qr);
            q[i].k=i;
        }
        sort(q,q+m,cmp);
        int l=1,r=0;
        for(int i=0;i<m;i++)
        {
            while(q[i].l<l)
                Add(--l);
            while(q[i].r>r)
                Add(++r);
            while(q[i].l>l) 
                Sub(l++);
            while(q[i].r<r) 
                Sub(r--);
            ans[q[i].k]=sum(q[i].qr+1)-sum(q[i].ql-1+1);
        }
        for(int i=0;i<m;i++)
            cout<<ans[i]<<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第十页

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值