洛谷 P2709 小B的询问(普通莫队+入门)

题目链接:

https://www.luogu.org/problemnew/show/P2709

题意:

小B有一个序列,包含N个1~K之间的整数。他一共有M个询问,每个询问给定一个区间[L…R],求 Σ ( c ( i ) 2 ) \Sigma(c(i)^2) Σ(c(i)2)的值,其中i的值从1到K,其中c(i)表示数字i在[L…R]中的重复次数。对于每个区间询问,输出一个 Σ ( c ( i ) 2 ) \Sigma(c(i)^2) Σ(c(i)2)

分析:

莫队分三种,普通莫队,树上莫队,带修改莫队;
莫队是离线操作,比较好的优化是分块;
个人理解: 什么是莫队算法呢,莫队算法即是对区间问题的处理与求解;

       首先它的核心是这样一个东西,如果你知道一个区间 [ l , r ] [l,r] [l,r]的结果(具体什么结果看题目要求),那么一般来说显然你可以 O ( 1 ) O(1) O(1)得到 [ l , r − 1 ] , [ l − 1 , r ] , [ l + 1 , r ] , [ l , r + 1 ] [l,r-1],[l-1,r],[l+1,r],[l,r+1] [l,r1],[l1,r],[l+1,r],[l,r+1]这些的区间的结果;

       莫队这里就是定义两个指针 l e f t , r i g h t left,right left,right,用来指向你所要处理或者查询区间的左右端点,然后你让这两个指针指向你所要处理的一堆区间中的某个区间左右端点,当你处理完第一个,使用它的结果并利用指针的 + + , − − ++,-- ++,操作,来移动指针,利用上面的所说的思想,一点一点 O ( 1 ) O(1) O(1)得到你想要的区间结果,即移动left,right直到它们恰好移动到你所需要的区间做右端点;(不理解没关系,做两道题目就差不多了);

       那么你显然会觉得这很暴力,正常来写,一般都会tle;在这个过程中你会想到在指针移动的过程中的时间开销是非常大的,你会想到把查询的区间按左端点排个序,然而这样考虑过于极端,如果有这样的区间 [ 1 , 100000 ] , [ 2 , 3 ] , [ 3 , 100000 ] [1,100000],[2,3],[3,100000] [1,100000],[2,3],[3,100000],如果这样,你显然也会tle;然后有些人就有了更加amazing的idea,分块,优先将区间按左端点所在块排序,其次按照区间右端点排序,经计算可得(我不会证明= =),当以 n \sqrt{n} n 进行分块时,将会达到一个最优的时间复杂度 N 1.5 \sqrt{N^{1.5}} N1.5 ,这就是普通莫队
http://www.cnblogs.com/Paul-Guderian/p/6933799.html (易理解)
https://blog.csdn.net/hzj1054689699/article/details/51866615
https://zhuanlan.zhihu.com/p/25017840

代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<queue>
#include<stack>
#include<cmath>
#include<set>
#include<map>

using namespace std;

#define  inf 0x7f7f7f7f
#define  maxn 50009
#define  N 2
#define mod 100003

typedef long long ll;
typedef struct {
    int u, v, next, w;
} Edge;
Edge e[N];
int cnt, head[N];

inline void add(int u, int v) {
    e[cnt].u = u;
    e[cnt].v = v;
    //e[cnt].w=w;
    // e[cnt].f=f;
    e[cnt].next = head[u];
    head[u] = cnt++;
    //e[cnt].u=v;
//    e[cnt].v=u;
//    e[cnt].w=0;
//    e[cnt].f=-f;
//    e[cnt].next=head[v];
//    head[v]=cnt++;
}

inline void write(int x) {
    if (x < 0)
        putchar('-'), x = -x;
    if (x > 9)
        write(x / 10);
    putchar(x % 10 + '0');
}

inline int read() {
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return x * f;
}
struct node{
    int l,r,id;
}p[maxn];
int an,n,m,k,a[maxn],pos[maxn],ans[maxn],c[maxn];
void update(int x,int val){
    an+=(2*val*c[a[x]]+1);
    c[a[x]]+=val;
}
bool com(node a,node b){
    if(pos[a.l]==pos[b.l])return a.r<b.r;
    else return pos[a.l]<pos[b.l];
}
int main() {
    cin>>n>>m>>k;
    int sz=sqrt(n);
    for(int i=1;i<=n;i++) {
        scanf("%d", &a[i]);
        pos[i]=i/sz;
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d",&p[i].l,&p[i].r);
        p[i].id=i;
    }
    sort(p+1,p+1+m,com);
    int l=1,r=0;
    for(int i=1;i<=m;i++){
        while(l>p[i].l){
            l--;
            update(l,1);
        }
        while(r<p[i].r){
            r++;
            update(r,1);
        }
        while(l<p[i].l){
            update(l,-1);
            l++;
        }
        while(r>p[i].r){
            update(r,-1);
            r--;
        }
        ans[p[i].id]=an;
    }
    for(int i=1;i<=m;i++){
        cout<<ans[i]<<endl;
    }
    return 0;
}

我们坚持一件事情,并不是因为这样做了会有效果,而是坚信,这样做是对的。
——哈维尔

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值