莫队算法模板 / p1494


前言

莫队算法是一个很好的离线算法(离线算法:查询与修改不同期),

它可以 O ( m n ) O(m \sqrt n) O(mn )时间进行查询,

它不像线段树那样有着较为严苛的前置条件,线段树要求待求量有着区间可加性

而莫队算法的前置条件是 O ( 1 ) O(1) O(1)进行区间转移:[L,R]->[L,R+1], [L,R]->[L,R-1] …等步长为一的转移

这个前置条件很宽松,甚至看到离线便可以主动向莫队这块靠

它的实现也并不困难,主要分为两个函数:modui(), move() 函数,

前者是算法主体,它首先对询问进行排序,第一关键字是 l 所在块编号,第二关键字是 r 大小,然后再初始化询问区间 interval0 = [0, -1], 再根据move函数单步区间转移,在 O ( n ) O(n) O(n)时间内获取第一个询问的ans,然后对后续查询进行区间转移,最后总转移时间为 O ( n n ) O(n \sqrt n) O(nn )

后者是单步区间转移的代价函数,直接对ans进行修改,不同的题会有不同的move()函数,这一部分也是能否做出题的关键,modui()函数则相对模板化,可直接调用,move()函数则更多需要一些推导

一、 例题 p1494


题目链接:洛谷p1494

二、 思路及代码

1. 思路

我们来思考一下move()函数,其余部分则直接套模板即可

这道题目我们将move()拆成了add()和del(),和一块比较乱

add(): [L, R] -> [L, R+1] ,我们将新增的袜子编号记为 i ,则 c[i] 的计数cnt[c[i]]++,那么答案的变化为 ( c n t [ c [ i ] ] + 1 2 ) − ( c n t [ c [ i ] ] 2 ) = c n t [ c [ i ] ] \Large{cnt[c[i]]+1 \choose 2}-{cnt[c[i]] \choose 2}=\small{cnt[c[i]]} (2cnt[c[i]]+1)(2cnt[c[i]])=cnt[c[i]]

del()也类似,变化为 c n t [ c [ i ] ] − 1 {cnt[c[i]]-1} cnt[c[i]]1

那么ok,剩余部分简单套模板即可

2. 代码

代码如下 :

#include <algorithm>
#include <cmath>
#include <iostream>
#define int long long
using namespace std;
const int maxn = 5e4 + 5;
int n, m, sqrtn;
int c[maxn];
int sum;
int cnt[maxn];
int ans1[maxn], ans2[maxn];
struct query {
  int l, r, id;
} q[maxn];
bool cmp(const query &a, const query &b) {
  if (a.l / sqrtn != b.l / sqrtn) return a.l < b.l;
  return (a.l / sqrtn) & 1 ? a.r < b.r : a.r > b.r;  // 奇偶优化
}
void add(int i) { sum += cnt[c[i]], cnt[c[i]]++; }
void del(int i) { cnt[c[i]]--, sum -= cnt[c[i]]; }
int gcd(int a, int b) { return b ? gcd(b, a % b) : a; }
void modui() {
  sort(q + 1, q + m + 1, cmp);
  int l = q[1].l, r = q[1].l - 1;
  for (int i = 1; i <= m; i++) {
    if (q[i].l == q[i].r) {  // 本题特判
      ans1[q[i].id] = 0, ans2[q[i].id] = 1;
      continue;
    }
    while (l > q[i].l) add(--l);  // 区间扩大
    while (r < q[i].r) add(++r);
    while (l < q[i].l) del(l++);  // 区间缩小
    while (r > q[i].r) del(r--);
    ans1[q[i].id] = sum;
    ans2[q[i].id] = (r - l + 1) * (r - l) / 2;
  }
}
signed main() {
  //   freopen("in.txt", "r", stdin);
  //   freopen("out.txt", "w", stdout);
  scanf("%d%d", &n, &m);
  sqrtn = sqrt(n);
  for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
  for (int i = 1; i <= m; i++) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
  modui();
  for (int i = 1; i <= m; i++) {
    if (ans1[i] != 0) {
      int g = gcd(ans1[i], ans2[i]);
      ans1[i] /= g, ans2[i] /= g;
    } else
      ans2[i] = 1;
    printf("%lld/%lld\n", ans1[i], ans2[i]);
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值