HYSBZ 2038 小z的袜子(hose) 莫队算法

莫队算法真是个神奇的算法。。。


构造曼哈顿距离生成树的搞法主要就是将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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目:使用AngularJs编写的简单 益智游戏(附源代码)  这是一个简单的 javascript 项目。这是一个拼图游戏,也包含一个填字游戏。这个游戏玩起来很棒。有两个不同的版本可以玩这个游戏。你也可以玩填字游戏。 关于游戏 这款游戏的玩法很简单。如上所述,它包含拼图和填字游戏。您可以通过移动图像来玩滑动拼图。您还可以选择要在滑动面板中拥有的列数和网格数。 另一个是填字游戏。在这里你只需要找到浏览器左侧提到的那些单词。 要运行此游戏,您需要在系统上安装浏览器。下载并在代码编辑器中打开此项目。然后有一个 index.html 文件可供您修改。在命令提示符中运行该文件,或者您可以直接运行索引文件。使用 Google Chrome 或 FireFox 可获得更好的用户体验。此外,这是一款多人游戏,双方玩家都是人类。 这个游戏包含很多 JavaScript 验证。这个游戏很有趣,如果你能用一点 CSS 修改它,那就更好了。 总的来说,这个项目使用了很多 javascript 和 javascript 库。如果你可以添加一些具有不同颜色选项的级别,那么你一定可以利用其库来提高你的 javascript 技能。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值