• [BZOJ3772]精神污染


    [BZOJ3772]精神污染

    试题描述

    兵库县位于日本列岛的中央位置,北临日本海,南面濑户内海直通太平洋,中央部位是森林和山地,与拥有关西机场的大阪府比邻而居,是关西地区面积最大的县,是集经济和文化于一体的一大地区,是日本西部门户,海陆空交通设施发达。濑户内海沿岸气候温暖,多晴天,有日本少见的贸易良港神户港所在的神户市和曾是豪族城邑“城下町”的姬路市等大城市,还有以疗养地而闻名的六甲山地等。
    兵库县官方也大力发展旅游,为了方便,他们在县内的N个旅游景点上建立了n-1条观光道,构成了一棵图论中的树。同时他们推出了M条观光线路,每条线路由两个节点x和y指定,经过的旅游景点就是树上x到y的唯一路径上的点。保证一条路径只出现一次。
    你和你的朋友打算前往兵库县旅游,但旅行社还没有告知你们最终选择的观光线路是哪一条(假设是线路A)。这时候你得到了一个消息:在兵库北有一群丧心病狂的香菜蜜,他们已经选定了一条观光线路(假设是线路B),对这条路线上的所有景点都释放了【精神污染】。这个计划还有可能影响其他的线路,比如有四个景点1-2-3-4,而【精神污染】的路径是1-4,那么1-3,2-4,1-2等路径也被视为被完全污染了。
    现在你想知道的是,假设随便选择两条不同的路径A和B,存在一条路径使得如果这条路径被污染,另一条路径也被污染的概率。换句话说,一条路径被另一条路径包含的概率。

    输入

    第一行两个整数N,M
    接下来N-1行,每行两个数a,b,表示A和B之间有一条观光道。
    接下来M行,每行两个数x,y,表示一条旅游线路。

    输出

    所求的概率,以最简分数形式输出。

    输入示例

    5 3
    1 2
    2 3
    3 4
    2 5
    3 5
    2 5
    1 4

    输出示例

    1/3

    数据规模及约定

    100%的数据满足:N,M<=100000

    题解

    先膜拜一下 popoqqq 的题解

    然后我再试图自己讲讲。。。

    一个很重要东西:一棵树上,一条链 A 被另一条链 B 包含当且仅当 A 的两个端点都在 B 上。

    那么对于一条链,要知道它是否包含,只需要关心它的两个端点就行了。

    对于一条链 (a, b),我们不妨把 b 称为 a 的对面儿。查询一条链 C 包含了哪些链的方法就是把所有在链 C 上的节点拎出来,分别统计它们的对面儿在不在链 C 上,而这个在链 C 上的“对面儿”们的个数就是 C 包含的链的条数。

    所以我们不妨建立一个树形的主席树,每个节点 u 在其父节点的基础之上把 u 的“对面儿”们的信息加进来。

    这个信息,具体指什么信息呢?我们发现我们需要回答有几个点在一条链上,所以可以对整棵树搞一个括号序列,那么这个信息,存节点在括号序列对应的位置就好了,注意一个节点在括号序列中对应两个位置,左括号的位置 +1,右括号的位置 -1。

    注意括号序列只能处理自己到祖先这样的链,所以对于一条 (a, b) 的链,在询问它包含几条链时把它拆成 (a, lca(a, b)) 和 (b, lca(a, b)) 这两条链就好了,小心 lca(a, b) 这个点有可能被重复统计,写代码时细心一点。

    #include <iostream>
    #include <cstdio>
    #include <algorithm>
    #include <cmath>
    #include <stack>
    #include <vector>
    #include <queue>
    #include <cstring>
    #include <string>
    #include <map>
    #include <set>
    using namespace std;
    
    const int BufferSize = 1 << 16;
    char buffer[BufferSize], *Head, *Tail;
    inline char Getchar() {
    	if(Head == Tail) {
    		int l = fread(buffer, 1, BufferSize, stdin);
    		Tail = (Head = buffer) + l;
    	}
    	return *Head++;
    }
    int read() {
    	int x = 0, f = 1; char c = Getchar();
    	while(!isdigit(c)){ if(c == '-') f = -1; c = Getchar(); }
    	while(isdigit(c)){ x = x * 10 + c - '0'; c = Getchar(); }
    	return x * f;
    }
    
    #define maxn 100010
    #define maxm 200010
    #define maxnode 4000010
    #define maxlog 17
    #define LL long long
    
    int ToT, sumv[maxnode], lc[maxnode], rc[maxnode];
    void update(int& y, int x, int l, int r, int p, int v) {
    	sumv[y = ++ToT] = sumv[x] + v;
    	if(l == r) return ;
    	int mid = l + r >> 1; lc[y] = lc[x]; rc[y] = rc[x];
    	if(p <= mid) update(lc[y], lc[x], l, mid, p, v);
    	else update(rc[y], rc[x], mid + 1, r, p, v);
    	return ;
    }
    int query(int o, int l, int r, int ql, int qr) {
    	if(!o) return 0;
    	if(ql <= l && r <= qr) return sumv[o];
    	int mid = l + r >> 1, ans = 0;
    	if(ql <= mid) ans += query(lc[o], l, mid, ql, qr);
    	if(qr > mid) ans += query(rc[o], mid + 1, r, ql, qr);
    	return ans;
    }
    
    int n, m, head[maxn], next[maxm], to[maxm];
    void AddEdge(int a, int b) {
    	to[++m] = b; next[m] = head[a]; head[a] = m;
    	swap(a, b);
    	to[++m] = b; next[m] = head[a]; head[a] = m;
    	return ;
    }
    struct Path {
    	int a, b;
    	Path() {}
    	Path(int _, int __): a(_), b(__) {}
    } ps[maxn];
    int pm, phead[maxn], pnxt[maxn], pto[maxn];
    void AddPath(int a, int b) {
    	ps[++pm] = Path(a, b);
    	pto[pm] = b; pnxt[pm] = phead[a]; phead[a] = pm;
    	return ;
    }
    
    int fa[maxlog][maxn], dep[maxn], rt[maxn], dl[maxn], dr[maxn], clo;
    void build(int u) {
    	dl[u] = ++clo;
    	for(int i = 1; i < maxlog; i++) fa[i][u] = fa[i-1][fa[i-1][u]];
    	for(int e = head[u]; e; e = next[e]) if(to[e] != fa[0][u]) {
    		dep[to[e]] = dep[u] + 1;
    		fa[0][to[e]] = u;
    		build(to[e]);
    	}
    	dr[u] = ++clo;
    	return ;
    }
    int lca(int a, int b) {
    	if(dep[a] < dep[b]) swap(a, b);
    	for(int i = maxlog - 1; i >= 0; i--) if(dep[a] - dep[b] >= (1 << i)) a = fa[i][a];
    	for(int i = maxlog - 1; i >= 0; i--) if(fa[i][a] != fa[i][b]) a = fa[i][a], b = fa[i][b];
    	return a == b ? a : fa[0][b];
    }
    void build2(int u) {
    	rt[u] = rt[fa[0][u]];
    	for(int e = phead[u]; e; e = pnxt[e]) {
    		update(rt[u], rt[u], 1, clo, dl[pto[e]], 1),
    		update(rt[u], rt[u], 1, clo, dr[pto[e]], -1);
    //		if(ToT % 10000 == 0) printf("ToT: %d
    ", ToT);
    	}
    	for(int e = head[u]; e; e = next[e]) if(to[e] != fa[0][u]) build2(to[e]);
    	return ;
    }
    
    LL gcd(LL a, LL b) { return b ? gcd(b, a % b) : a; }
    
    int main() {
    //	freopen("data.in", "r", stdin);
    	n = read(); int tm = read();
    	for(int i = 1; i < n; i++) {
    		int a = read(), b = read();
    		AddEdge(a, b);
    	}
    	while(tm--) {
    		int a = read(), b = read();
    		AddPath(a, b);
    	}
    	
    	build(1); build2(1);
    	LL ans = 0;
    	for(int i = 1; i <= pm; i++) {
    		int a = ps[i].a, b = ps[i].b, c = lca(a, b);
    //		printf("lca(%d, %d) = %d
    ", a, b, c);
    		ans += (LL)query(rt[a], 1, clo, dl[c], dl[a]) - query(rt[fa[0][c]], 1, clo, dl[c], dl[a]);
    		ans += (LL)query(rt[a], 1, clo, dl[c] + 1, dl[b]) - query(rt[fa[0][c]], 1, clo, dl[c] + 1, dl[b]);
    		ans += (LL)query(rt[b], 1, clo, dl[c], dl[a]) - query(rt[c], 1, clo, dl[c], dl[a]);
    		ans += (LL)query(rt[b], 1, clo, dl[c] + 1, dl[b]) - query(rt[c], 1, clo, dl[c] + 1, dl[b]);
    		ans--;
    	}
    	
    	LL bns = (LL)pm * (pm - 1) >> 1, g = gcd(ans, bns);
    	ans /= g; bns /= g;
    	printf("%lld/%lld
    ", ans, bns);
    	
    	return 0;
    }
    
  • 相关阅读:
    进程隐藏类
    MFC中一些使用的方法
    c++/vc++的一些学习方法
    随意更改桌面壁纸
    Winsock异步模型之(事件通知模型 WSAAsyncSelect)
    开发外挂的一些原理
    ios 图片
    得到cell的坐标
    取出字符串中的空格
    ios 获取当前的日期,年月日
  • 原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/6358722.html
Copyright © 2020-2023  润新知