莫队学习总结

小Z的袜子 (HYSBZ-2038)

作为一个生活散漫的人,小Z每天早上都要耗费很久从一堆五颜六色的袜子中找出一双来穿。终于有一天,小Z再也无法忍受这恼人的找袜子过程,于是他决定听天由命…… 具体来说,小Z把这N只袜子从1到N编号,然后从编号L到R(L 尽管小Z并不在意两只袜子是不是完整的一双,甚至不在意两只袜子是否一左一右,他却很在意袜子的颜色,毕竟穿两只不同色的袜子会很尴尬。 你的任务便是告诉小Z,他有多大的概率抽到两只颜色相同的袜子。当然,小Z希望这个概率尽量高,所以他可能会询问多个(L,R)以方便自己选择。

思路:模板题

某个区间\([l,r]\)的答案:
\[ {\sum_{i为出现的颜色} sum[i]\cdot (sum[i]-1)/2}\over {C_{r-l+1}^2} \]

  1. 对询问排序
    1. \([l,r]\),以\(l\)所在块的编号为第一关键字,r为第二关键字从小到大排序。
  2. 暴力维护答案的分子部分即可
  3. 可以发现答案分子分母同时将2约掉,分子展开后变成\(sum[i]\cdot sum[i] - sum[i]\)可以发现对于所有的 i ,\(sum[i]\)的和将变成\(r-l+1\),所以我们只需要维护所有\(sum[i]\)的平方和即可
#include <iostream>
#include <algorithm>
#include <math.h>
#include <cstdio>
using namespace std;
const int N = 50010;
typedef long long ll;
int a[N],be[N],n,m;
ll res = 0,sum[N];
struct node{
    int l,r,id;
    ll A,B;
}q[N];
//如果在同一块,则按照右端点排序,否则按照左端点
bool cmp1(node a,node b){
    return be[a.l] == be[b.l] ? a.r < b.r : a.l < b.l;
}
bool cmp(node a,node b){
    return a.id < b.id;
}
ll gcd(ll a,ll b){return b == 0 ? a : gcd(b,a%b);}
ll S(ll x){return x * x;}
//先减去上一次的影响,修改后再重新加新的影响
void move(int pos,int add){res -= S(sum[a[pos]]);sum[a[pos]] += add;res += S(sum[a[pos]]);}
int main(){
    scanf("%d%d",&n,&m);
    int base = sqrt(n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);q[i].id = i;
        be[i] = (i-1) / base + 1;//be[i]即i在第几块
    }
    for(int i=1;i<=m;i++)scanf("%d%d",&q[i].l,&q[i].r);
    sort(q+1,q+1+m,cmp1);
    int l = 1,r = 0;
    res = 0;//res为当点询问区间内出现的所有颜色的个数平方和
    for(int i=1;i<=m;i++){
        //暴力调整区间,维护res
        while(l < q[i].l)move(l++,-1);
        while(l > q[i].l)move(--l,1);
        while(r < q[i].r)move(++r,1);
        while(r > q[i].r)move(r--,-1);
        if(l == r){
            q[i].A = 0;q[i].B = 1;continue;
        }
        q[i].A  = res - (r - l + 1);//计算答案分子部分
        q[i].B = 1ll * (r - l + 1) * (r - l);//分母部分
        ll g = gcd(q[i].A,q[i].B);//约分
        q[i].A /= g;
        q[i].B /= g;
    }
    sort(q+1,q+1+m,cmp);
    for(int i=1;i<=m;i++){
        printf("%lld/%lld\n",q[i].A,q[i].B);
    }
    return 0;
}

普通莫队优化

可以发现当第一块内的询问处理完之后,r的位置应该特别靠后,但是当移动到下一个块之后,r可能会往前移动很多,比如如下询问

//第一个块
1 50
2 100
//第二个块
12 13
14 100

在完成[2,100]的询问后,r从100-> 13 然后又从13 -> 100。这样显然不如100->100, 100 -> 13。

如何优化?

相邻两块之间r的排序规则相反即可

即奇数块按照升序,偶数快按照降序

Result Memory Time
Accepted 3456 kb 1840 ms
Accepted 3456 kb 1392 ms

下面的是优化过的。

bool cmp1(node a,node b){
    return be[a.l] == be[b.l] ? 
        (be[a.l]&1 ? a.r < b.r : a.r > b.r)
        : a.l < b.l;
}

带修改的莫队

考虑普通莫队加入修改修做,如果修改操作可以\(O(1)\)的应用以及撤销(同时也要维护当前区间的答案),那么可以在\(O(n^{5\over 3})\)的复杂度内求出所有询问的答案。

实现: 离线后排序,顺序遍历询问,先将时间转移到当前询问的时间,然后再像普通莫队一样转移区间。

排序方法: 设定块的长度为\(S_1,S_2\),按照(\(\lfloor{l\over S_1}\rfloor \lfloor{r\over S_2}\rfloor,t\))的三元组小到大排序,其中 \(t\) 表示这个询问的时刻之前经历过了几次修改操作

复杂度分析:考虑询问序列中的每个小块,小块内每个询问的一二关键字相同。在这个小块内,时间 \(t\) 最多变化 \(m\) ,对于每个询问,\(l,r\) 最多变化 \(S_1,S_2\), 一共右\(n^2\over {S_1,S_2}\) 个这样的块,相邻块之间转移复杂度是\(O(n)\), 总复杂度就是

\(O(mS_1+mS_2+{n^2m\over S_1S_2}+{n^3\over S_1S_2})\)

\(n,m\)同阶时,取\(S_1 = S_2 = n^{2\over 3}\) 时可达到最优复杂度\(O(n^{5\over 3})\)

int l = 0, r = 0, t = 0, nowAns = 0;

inline void move(int pos, int sign) {
    // update nowAns
}

inline void moveTime(int t, int sign) {
    // apply or revoke modification
    // update nowAns
}

void solve() {
    BLOCK_SIZE = int(ceil(pow(n, 2.0 / 3)));
    sort(querys, querys + m);
    for (int i = 0; i < q1; ++i) {
        const query q = querys[i];
        while (t < q.t) moveTime(t++, 1);
        while (t > q.t) moveTime(--t, -1);
        while (l < q.l) move(l++, -1);
        while (l > q.l) move(--l, 1);
        while (r < q.r) move(r++, 1);
        while (r > q.r) move(--r, -1);
        ans[q.id] = nowAns;
    }
}

猜你喜欢

转载自www.cnblogs.com/1625--H/p/11329586.html