HYSBZ2038 小Z的袜子(莫队算法)

原地址:http://www.cnblogs.com/chanme/p/3681999.html
今天学了一下传说中的解决离线询问不修改的一种算法。题目的意思非常简单,就是询问在一个[L,R]区间里的取两个物品,然后这两个物品颜色相同的概率。其实就是对于每种颜色i,这个区间里对应的个数cnt[i],那么答案就应该是 sigma (cnt[i]cnt[i-1]) / (R-L+1)(R-L). 问题是要是每次询问我都遍历一遍的话必T无疑。这个时候莫队算法就给出了其中一种非常重要的离线处理方法,通过合适的安排询问的次序降低一定的复杂度。

举个例子,假如我询问的时候是询问[1,2],[1,3],[1,4],[1,5],[1,6]…[1,7],[2,7],显然我们每次计算这些答案都是O(1)的。但是假如询问的时候次序变一下

[2,7],[1,3],[1,7],[1,2]….这样子的话我们就会很蛋疼,所以通过合理的安排区间询问的顺序可以降低复杂度,而莫队算法就是这样做的。

首先假如我们已经知道了[L,R]的答案,如果我们可以O(1)的时间得出[L+1,R],[L-1,R],[L,R+1],[L,R-1]的话,那么下面这种算法就是可以实施的,我们通过对询问分块处理,假如区间长度为n,那么我们分成sqrt(n)块,然后对区间排序,排序的时候按照左端点属于那个块先排序,然后再按右端点的大小排序。排完序之后,我们就根据当前的[L,R]不断地去算下一个区间的[Li,Ri].其中就是根据L,Li和R,Ri的差值去变化求出对应的答案。

下面看下复杂度。我们总是从左到右处理一系列的询问,所以对于同一块里的询问如果有k个的话,那么由于左端点总是在同一个块里动,所以每次左端点动是O(sqrt(n)),而右端点递增,所以k次的总复杂度不会超过n,因此对于同一块的操作的总复杂度应该为 O(k*sqrt(n)+n)

假如询问的总数为m的话,那么由于最多有sqrt(n)块,所以总复杂度是 m*sqrt(n)+n*sqrt(n).

还有就是当由一个区间转换到相邻的另一个区间的时候,左端点移动不超过O(sqrt(n)),右端点移动不超过O(n),最多变动sqrt(n)次。所以转换区间的时候的复杂度是sqrt(n)*sqrt(n)+n*sqrt(n).

综上我们可以看出最后的复杂度应该是n^1.5.

这种神奇的分块处理原来还只是一种简约版。由上面可以看出,两次转移的复杂度取决于[L,R] [Li,Ri]的曼哈顿距离 abs(L-Li)+abs(R-Ri),所以如果能够合适安排这样的距离使得总的曼哈顿距离最小,那么还能再进一步优化,涉及到了曼哈顿最小生成树的概念,反正我是没看懂,今天学下这种分块的思想练练手~

#pragma warning (disable:4996)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;

#define maxn 55000
#define ll long long

ll color[maxn];
ll cnt[maxn];
int n, m;

int pos[maxn];
struct Query
{
    int l, r,id;
    bool operator < (const Query &b) const{
        return pos[l] == pos[b.l] ? r < b.r : pos[l] < pos[b.l];
    }
}q[maxn];

ll gcd(ll a, ll b){
    return a&&b ? gcd(b, a%b) : a + b;
}

ll resl[maxn], resr[maxn];

int main()
{
    while (cin >> n >> m)
    {
        for (int i = 1; i <= n; i++){
            scanf("%lld", color + i);
        }
        for (int i = 1; i <= m; i++){
            scanf("%d%d", &q[i].l, &q[i].r);
            q[i].id = i;
        }
        int bas = int(sqrt(n + .5));
        for (int i = 1; i <= n; i++){
            pos[i] = i / bas;
        }
        memset(cnt, 0, sizeof(cnt));
        sort(q + 1, q + 1 + m);
        int l = 1, r = 1; ll ans = 0;
        cnt[color[1]] ++;
        for (int i = 1; i <= m; i++){
            if (r < q[i].r){
                for (int k = r + 1; k <= q[i].r; k++){
                    ans -= cnt[color[k]] * (cnt[color[k]] - 1);
                    cnt[color[k]]++;
                    ans += cnt[color[k]] * (cnt[color[k]] - 1);
                }
            }
            else if (r>q[i].r){
                for (int k = r ; k >= (q[i].r+1); k--){
                    ans -= cnt[color[k]] * (cnt[color[k]] - 1);
                    cnt[color[k]]--;
                    ans += cnt[color[k]] * (cnt[color[k]] - 1);
                }
            }
            if (l < q[i].l){
                for (int k = l; k <= q[i].l-1; k++){
                    ans -= cnt[color[k]] * (cnt[color[k]] - 1);
                    cnt[color[k]]--;
                    ans += cnt[color[k]] * (cnt[color[k]] - 1);
                }
            }
            else if (l>q[i].l){
                for (int k = l - 1; k >= q[i].l; k--){
                    ans -= cnt[color[k]] * (cnt[color[k]] - 1);
                    cnt[color[k]]++;
                    ans += cnt[color[k]] * (cnt[color[k]] - 1);
                }
            }
            l = q[i].l; r = q[i].r;
            ll len = q[i].r - q[i].l+1;
            ll tot = len*(len - 1);
            ll g = gcd(ans, tot);
            resl[q[i].id] = ans / g; resr[q[i].id] = tot / g;
        }
        for (int i = 1; i <= m; i++){
            printf("%lld/%lld\n", resl[i], resr[i]);
        }
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值