【BZOJ 2038】小Z的袜子&莫队算法详解

莫队算法裸题
显然对于一个区间[l,r],可以在O(1)的时间里转移到[l,r+1]或[l,r-1]。
证明:
ans=(sum(i)(sum(i)1)/2)(RL+1)(RL)/2
其中sum(i)表示第i种颜色的袜子的数量,把这个式子化简得到:
ans=sum(i)2(RL+1)(RL+1)(RL)
这样我们只要统计p = sum(i)2 就可以了。当某一种袜子的颜色从c变成c+1时, p=pc2+(c+1)2=p+2c+1 ,同理从c变成c-1时, p=p+c2(c+1)2=p2c1 。也许位运算会快那么一点?(划掉)

然后对整个区间分块,对所有查询双关键字排序,第一关键字为所在的块,第二关键字是查询右端点。然后一个一个加减区间即可。
时间复杂度证明:
左端点的证明:
1、两个查询的左端点在同一块中:每次最多移动 N 次,一共 N 次,时间复杂度为O(NN);
2、两个查询不在同一块中:每次跨越相邻块最多要移动 2N 次,这样的操作最多只有 N 次,所以时间复杂度为O( N2 )。
右端点的证明:
1、两个查询的左端点在同一块中:右端点递增,一共移动 N 次,一共有N块,时间复杂度为O( NN );
2、两个查询不在同一块中:每次跨越相邻块右端点重新回到左侧,最多移动 N 次,这样的操作最多只有N次,所以时间复杂度为O( NN )。

#include<cmath>
#include<cstdio>
#include<vector>
#include<queue>
#include<cstring>
#include<iomanip>
#include<stdlib.h>
#include<iostream>
#include<algorithm>
#define ll long long
#define inf 1000000000
#define mod 1000000007
#define N 100000
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
struct query{int l,r,id;} q[N];
int block_pos[N],sum[N],c[N];
ll resa[N],resb[N];
int n,m,i,block,l,r,p;
ll ans,k;
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b);}
bool cmp(const query &a,const query &b)
{
    if (block_pos[a.l] == block_pos[b.l]) return a.r < b.r;
    return a.l < b.l;
}
void update(int p,int delta)
{
    ans = ans + (sum[c[p]] * 2 + delta) * delta;
    sum[c[p]] += delta;
}
int main()
{
    scanf("%d%d",&n,&m);
    fo(i,1,n) scanf("%d",&c[i]);
    fo(i,1,m) scanf("%d%d",&q[i].l,&q[i].r);
    fo(i,1,m) q[i].id = i;
    block = sqrt(n);
    fo(i,1,n) block_pos[i] = (i - 1) / block + 1;
    sort(q+1,q+m+1,cmp);
    l = 1; r = 0;
    fo(i,1,m)
        {
            while (r < q[i].r) {update(r+1,1); r++;}
            while (r > q[i].r) {update(r,-1); r--;}
            while (l < q[i].l) {update(l,-1); l++;}
            while (l > q[i].l) {update(l-1,1); l--;}
            p = q[i].id;
            resa[p] = ans - (q[i].r - q[i].l + 1);
            resb[p] = (ll)(q[i].r - q[i].l + 1) * (q[i].r - q[i].l);
            k = gcd(resa[p],resb[p]);
            resa[p] /= k; resb[p] /= k; 
        }
    fo(i,1,m) printf("%lld/%lld\n",resa[i],resb[i]);
    return 0;   
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值