莫队算法裸题
显然对于一个区间[l,r],可以在O(1)的时间里转移到[l,r+1]或[l,r-1]。
证明:
ans=∑(sum(i)∗(sum(i)−1)/2)(R−L+1)∗(R−L)/2
其中sum(i)表示第i种颜色的袜子的数量,把这个式子化简得到:
ans=∑sum(i)2−(R−L+1)(R−L+1)∗(R−L)
这样我们只要统计p =
∑sum(i)2
就可以了。当某一种袜子的颜色从c变成c+1时,
p=p−c2+(c+1)2=p+2∗c+1
,同理从c变成c-1时,
p=p+c2−(c+1)2=p−2∗c−1
。也许位运算会快那么一点?(划掉)
然后对整个区间分块,对所有查询双关键字排序,第一关键字为所在的块,第二关键字是查询右端点。然后一个一个加减区间即可。
时间复杂度证明:
左端点的证明:
1、两个查询的左端点在同一块中:每次最多移动
N−−√
次,一共
N
次,时间复杂度为O(
2、两个查询不在同一块中:每次跨越相邻块最多要移动
2N−−√
次,这样的操作最多只有
N−−√
次,所以时间复杂度为O(
N−−√2
)。
右端点的证明:
1、两个查询的左端点在同一块中:右端点递增,一共移动
N
次,一共有
2、两个查询不在同一块中:每次跨越相邻块右端点重新回到左侧,最多移动
N
次,这样的操作最多只有
#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;
}