莫队算法真是个神奇的算法。。。
构造曼哈顿距离生成树的搞法主要就是将m个询问[l,r]看成二维平面上的点,如果从区间[l,r]的查询可以O(1)地转移到[l,r+1], 那么从[l,r]转移到[l',r']的花费就是|l-l'|+|r-r'|,也就是曼哈顿距离, 如果构造出m个询问曼哈顿距离最小生成树的话,在树上进行转移,树边的曼哈顿距离之和的级别是nsqrt(n)的(并不知道怎么证明。。。), 然后撸出答案就行了。构造曼哈顿距离生成树是nlog(n)的,转移是nsqrt(n), 总的复杂度是nsqrt(n)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 50020
#define M 200020
#define LL long long
#define inf 0x3f3f3f3f
int a[N], n, m;
int x[N], y[N], id[N];
LL ans[N];
struct edge {
int u, v, c;
bool operator < (const edge &b) const {
return c < b.c;
}
}b[M];
int tot;
int san[N], cnt;
int cmp(int i, int j) {
if(x[i] != x[j]) return x[i] < x[j];
return y[i] < y[j];
}
int mi[N], pos[N], fa[N];
int fst[N], nxt[M], vv[M], e;
LL X, cc[N];
void init() {
memset(fst, -1, sizeof fst);
e = 0;
}
void add(int u, int v) {
vv[e] = v, nxt[e] = fst[u], fst[u] = e++;
}
void add_edge(int u, int v) {
++tot;
b[tot].u = u, b[tot].v = v;
b[tot].c = abs(x[u] - x[v]) + abs(y[u] - y[v]);
}
void update(int x, int v, int p) {
while(x) {
if(mi[x] > v)
mi[x] = v, pos[x] = p;
x -= x & -x;
}
}
int query(int x) {
int res = inf, ret = -1;
while(x <= cnt) {
if(res > mi[x])
res = mi[x], ret = pos[x];
x += x & -x;
}
return ret;
}
int find(int x) {
if(fa[x] != x) fa[x] = find(fa[x]);
return fa[x];
}
int haxi(int x) {
return lower_bound(san + 1, san + cnt + 1, x) - san;
}
void ManMst() {
tot = 0;
for(int dir = 0; dir < 4; ++dir) {
if(dir == 1 || dir == 3) {
for(int i = 1; i <= n; ++i)
swap(x[i], y[i]);
}
else if(dir == 2) {
for(int i = 1; i <= m; ++i)
x[i] = -x[i];
}
cnt = 0;
for(int i = 1; i <= m; ++i) {
id[i] = i;
san[++cnt] = y[i] - x[i];
pos[i] = -1;
mi[i] = inf;
}
sort(id + 1, id + m + 1, cmp);
sort(san + 1, san + cnt + 1);
cnt = unique(san + 1, san + cnt + 1) - san - 1;
for(int i = m; i >= 1; --i) {
int u = haxi(y[id[i]] - x[id[i]]);
int v = query(u);
if(v != -1)
add_edge(id[i], v);
update(u, x[id[i]] + y[id[i]], id[i]);
}
}
for(int i = 1; i <= m; ++i) {
fa[i] = i;
y[i] = -y[i];
}
init();
sort(b + 1, b + tot + 1);
for(int i = 1; i <= tot; ++i) {
int u = b[i].u, v = b[i].v;
if(find(u) != find(v)) {
add(u, v);
add(v, u);
fa[find(u)] = find(v);
}
}
}
void addIt(int l, int r, int v) {
l = max(l, 1);
r = min(r, n);
for(int i = l; i <= r; ++i) {
X -= cc[a[i]] * (cc[a[i]] - 1) / 2;
cc[a[i]] += v;
X += cc[a[i]] * (cc[a[i]] - 1) / 2;
}
}
void dfs(int l, int r, int ll, int rr, int u, int pre) {
if(ll < l) addIt(ll, l - 1, 1);
if(rr > r) addIt(r + 1, rr, 1);
if(ll > l) addIt(l, ll - 1, -1);
if(rr < r) addIt(rr + 1, r, -1);
ans[u] = X;
for(int i = fst[u]; ~i; i = nxt[i]) {
int v = vv[i];
if(v == pre) continue;
dfs(ll, rr, x[v], y[v], v, u);
}
if(ll < l) addIt(ll, l - 1, -1);
if(rr > r) addIt(r + 1, rr, -1);
if(ll > l) addIt(l, ll - 1, 1);
if(rr < r) addIt(rr + 1, r, 1);
}
LL gcd(LL a, LL b) {
while(a && b && (a > b? a %= b: b %= a));
return a + b;
}
int main() {
// freopen("tt.txt", "r", stdin);
while(scanf("%d%d", &n, &m) != EOF) {
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for(int i = 1; i <= m; ++i) scanf("%d%d", &x[i], &y[i]);
ManMst();
X = 0;
memset(cc, 0, sizeof cc);
dfs(0, 0, x[1], y[1], 1, -1);
for(int i = 1; i <= m; ++i) {
LL len = y[i] - x[i] + 1;
len = len * (len - 1) / 2;
if(ans[i] == 0) {
puts("0/1");
continue;
}
LL g = gcd(ans[i], len);
printf("%lld/%lld\n", ans[i] / g, len / g);
}
}
return 0;
}
然而当学会曼哈顿距离生成树之后, 不久你就会发现有一个曼哈顿距离生成树精简的替代品(卧槽, 4000b的代码用1500b就搞完了, 卧槽, 有了这个之后绝逼再也不会写曼哈顿距离生成树了), 就是先将n个数分成sqrt(n)块, 然后对询问[l,r],以l所在的块为第一关键字,r为第二关键字排序,然后按照排序后的顺序进行转移就行了。
可以发现, 对于一个块内的询问, r从小到大转移长度最多O(n)次,sqrt(n)个块,所以总的转移是nsqrt(n), l最多有O(n)次移, 每次转移长度不超过sqrt(n),所以总的转移是nsqrt(n), 所以nsqrt(n)就搞出来了。。。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
#include <ctime>
#include <algorithm>
#include <cmath>
#include <queue>
using namespace std;
#define N 50002
#define LL long long
int B;
int pos[N];
int n, m, a[N];
int x[N], y[N], id[N];
LL X, ans[N];
LL s[N];
LL gcd(LL x, LL y) {
while(x && y && (x > y? x %= y: y %= x));
return x + y;
}
bool cmp(int i, int j) {
if(pos[x[i]] != pos[x[j]])
return pos[x[i]] < pos[x[j]];
return y[i] < y[j];
}
void addIt(int l, int r, int v) {
for(int i = l; i <= r; ++i) {
X -= s[a[i]] * (s[a[i]] - 1);
s[a[i]] += v;
X += s[a[i]] * (s[a[i]] - 1);
}
}
int main() {
while(scanf("%d%d", &n, &m) != EOF) {
B = sqrt(n * 1.0);
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
pos[i] = i * 1.0 / B;
}
for(int i = 1; i <= m; ++i) {
scanf("%d%d", &x[i], &y[i]);
id[i] = i;
}
sort(id + 1, id + m + 1, cmp);
int l = 1, r = 0;
memset(s, 0, sizeof s);
for(int i = 1; i <= m; ++i) {
int u = id[i];
if(x[u] < l) addIt(x[u], l - 1, 1);
if(y[u] > r) addIt(r + 1, y[u], 1);
if(l < x[u]) addIt(l, x[u] - 1, -1);
if(r > y[u]) addIt(y[u] + 1, r, -1);
l = x[u], r = y[u];
ans[u] = X;
}
for(int i = 1; i <= m; ++i) {
if(ans[i] == 0) {
puts("0/1");
continue;
}
LL len = y[i] - x[i] + 1;
len = len * (len - 1);
LL g = gcd(ans[i], len);
printf("%lld/%lld\n", ans[i] / g, len / g);
}
}
return 0;
}