洛谷P2709.小B的询问 (普通莫队)

本文分享了一次使用莫队算法解决区间问题的成功经验,通过维护区间内数字出现次数的平方和,实现了高效的数据处理。文章详细介绍了算法实现过程,包括分块、排序、add和del函数的实现,并附上了完整代码。

2020.5.18
还是蛮有收获的,谁也没想到第一次自己写莫队能不看题解1A,激动。

这道题就是维护区间内所有数字出现次数的平方和。这要在平时肯定很难搞,至少我是想不出任何能够处理这个区间问题的树状结构,现在有莫队算法就可以做了。

首先老一套,分块,排序,这些都不用讲。主要是add和del函数,我们观察到,k范围比较小,可以开数组记录每个数字的出现次数,然后当区间加入一个数字的时候,首先要把上一层的贡献撤掉,就是cnt[a[x]]的平方,然后给这个数字的贡献再加上1,之后再在答案里加上平方就可以了,这些东西可以在O(1)时间内从l和r转移过来,然后套上莫队,就ac了,我也不知道为什么我的答案会比所有的标准答案大1,然后-1就ac了。
上代码:

#include <bits/stdc++.h>
using namespace std;
#define limit (1000000 + 5)//防止溢出
#define INF 0x3f3f3f3f
#define inf 0x3f3f3f3f3f
#define lowbit(i) i&(-i)//一步两步
#define EPS 1e-6
#define FASTIO  ios::sync_with_stdio(false);cin.tie(0);
#define ff(a) printf("%lld\n",a );
#define pi(a,b) pair<a,b>
#define rep(i, a, b) for(int i = a ; i <= b ; ++i)
#define per(i, a, b) for(int i = b ; i >= a ; --i)
#define mint(a,b,c) min(min(a,b), c)
#define MOD 998244353
#define FOPEN freopen("C:\\Users\\administrator01\\CLionProjects\\untitled24\\data.txt", "rt", stdin)
typedef long long ll;
typedef unsigned long long ull;
ll read(){
    ll sign = 1, x = 0;char s = getchar();
    while(s > '9' || s < '0' ){if(s == '-')sign = -1;s = getchar();}
    while(s >= '0' && s <= '9'){x = x * 10 + s - '0';s = getchar();}
    return x * sign;
}//快读
void write(ll x){
    if(x / 10) write(x / 10);
    putchar(x % 10 + '0');
}
int n, m, k;
struct node{
    int l, r, qid, blo;
    bool operator<(const node &rhs)const{
        if(blo ^ rhs.blo)return l < rhs.l;
        return blo & 1 ? r < rhs.r : rhs.r < r;
    }
}query[limit];
int cnt[limit],a[limit];
ll ans[limit];
ll res;
ll sq(int x){
    return x * x;
}
void add(int x){
    res -= sq(cnt[a[x]]);
    res += sq(++cnt[a[x]]);
}
void del(int x){
    res -= sq(cnt[a[x]]);
    res += sq(--cnt[a[x]]);
}
int main(){
#ifdef LOCAL
    FOPEN;
    //freopen("C:\\Users\\administrator01\\CLionProjects\\untitled24\\out.txt", "w", stdout);
#endif
    n = read(), m = read(), k = read();
    rep(i ,1, n)a[i] = read();
    int block = int(sqrt(n >= 3 ? n * (2.0 / 3) : n));//分块
    rep(i, 1, m){
        query[i].l = read(), query[i].r = read(), query[i].qid = i;
        query[i].blo = query[i].l / block;//分成块
    }
    sort(query + 1, query + 1 + m);
    int l = 0 , r = 0;
    res = 0;
    rep(i ,1, m){
        while(l < query[i].l)del(l++);//缩进
        while(l > query[i].l)add(--l);
        while(r < query[i].r)add(++r);
        while(r > query[i].r)del(r--);
        ans[query[i].qid] = res;
    }
    rep(i ,1, m)write(ans[i] - 1),puts("");
    return 0;
}
```markdown ### 题目重述 #### P5722:数列求和 输入正整数 $n$(≤1000),求 $1 + 2 + \cdots + n$ 的和。 #### P5723:质数口袋 输入正整数 $L$(≤100000),依次放入质数,直到已放入的质数总和超过 $L$ 为止(最后一个超过的不放)。输出能放进“口袋”的所有质数,并统计个数。 #### P1217:回文质数(简化版思路) 输入两个整数 $a$ 和 $b$($5 \leq a < b \leq 100000000$),输出区间 $[a, b]$ 内的所有既是回文又是质数的数。但本题数据量大,需优化。 --- ### 给出答案(独立完整代码) #### ✅ P5722 数列求和 ```c #include <stdio.h> int main() { int n; scanf("%d", &n); printf("%d\n", n * (n + 1) / 2); return 0; } ``` --- #### ✅ P5723 质数口袋 ```c #include <stdio.h> #include <math.h> int isPrime(int x) { if (x < 2) return 0; if (x == 2) return 1; if (x % 2 == 0) return 0; for (int i = 3; i <= sqrt(x); i += 2) { if (x % i == 0) return 0; } return 1; } int main() { int L; scanf("%d", &L); int sum = 0, count = 0; for (int i = 2; ; i++) { if (isPrime(i)) { if (sum + i > L) break; sum += i; count++; printf("%d\n", i); } } printf("%d\n", count); return 0; } ``` --- #### ✅ P1217 回文质数(C语言可行版本,含优化) ```c #include <stdio.h> #include <string.h> #include <math.h> int isPrime(int x) { if (x < 2) return 0; if (x == 2) return 1; if (x % 2 == 0) return 0; for (int i = 3; i <= sqrt(x); i += 2) { if (x % i == 0) return 0; } return 1; } int isPalindrome(int x) { int original = x, reversed = 0; while (x > 0) { reversed = reversed * 10 + x % 10; x /= 10; } return original == reversed; } int main() { int a, b; scanf("%d %d", &a, &b); // 特判偶数位回文:除11外都不是质数(可加速) for (int i = a; i <= b; i++) { if (i > 9 && i < 100 && i % 11 != 0) continue; // 偶位数如10~99中只有11可能 if (i > 1000 && i < 10000 && i % 11 != 0) continue; // 4位回文必须被11整除 if (isPalindrome(i) && isPrime(i)) { printf("%d\n", i); } } return 0; } ``` --- ### 代码解析 **P5722**:使用等差数列求和公式 $S_n = \frac{n(n+1)}{2}$,避免循环。 **P5723**:从2开始枚举每个数,判断是否为质数;若加入后总和不超过 $L$,则放入并计数。 **P1217**:同时判断回文与质数。由于范围大,加入剪枝: - 所有偶数位回文数(如4位、6位)都能被11整除 → 只有11是合法偶位回文质数。 - 可跳过部分明显非回文或非质数的情况。 --- ### 知识点(列出该代码中遇到的知识点) - **等差数列求和公式**:直接计算 $1$ 到 $n$ 的和,时间复杂度 $O(1)$。 - **质数判断(试除法)**:只需检查到 $\sqrt{x}$,且可跳过偶数提高效率。 - **回文数判断**:通过反转数字比较原值,适用于整数回文检测。 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值