传送门:点击打开链接
题意:有一个庞大的家族,共n人。已知这n个人的祖辈关系正好形成树形结构(即父亲向儿子连边)。
在另一个未知的平行宇宙,这n人的祖辈关系仍然是树形结构,但他们相互之间的关系却完全不同了,原来的祖先可能变成了后代,后代变成的同辈……整个家族的亲密度定义为任意两个人亲密度的总和。
思路:这道题的主席树用的太巧妙了~
我们考虑点对对u节点做的贡献,如果在两棵树中u的子树的公共节点有x个,那么贡献就是x*(x-1)/2
所以我们的重点就是,如何来求公共节点个数。
我们首先对第一棵树求一遍DFS序,对每个节点编号,让树型结构变成线段结构。
但是求DFS序后,建主席树的时候,一定要搞清楚u和区间位置的关系,可以用个数组ToU来让思路更清楚
然后,再对第二棵树求一遍DFS序,对每个节点编号,那么对于一个节点u,在第二棵树对应的区间为[L,R],在第一棵树对应的区间为[l,r],那么x就等于[l,r]这些数在区间[L,R]里出现的次数,很明显我们能用主席树来维护。代码如下:
#include <map> #include <set> #include <cmath> #include <ctime> #include <stack> #include <queue> #include <cstdio> #include <cctype> #include <bitset> #include <string> #include <vector> #include <cstring> #include <iostream> #include <algorithm> #include <functional> #define fuck(x) cout<<"["<<x<<"]"; #define FIN freopen("input.txt","r",stdin); #define FOUT freopen("output.txt","w+",stdout); //#pragma comment(linker, "/STACK:102400000,102400000") using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef long long LL; const int MX = 2e6 + 5; const int MU = 1e5 + 5; struct Edge { int v, nxt; } E[MU * 2]; int Head[MU], erear; void edge_init() { erear = 0; memset(Head, -1, sizeof(Head)); } void edge_add(int u, int v) { E[erear].v = v; E[erear].nxt = Head[u]; Head[u] = erear++; } int n, dfn, OUT[MU], o[MU], ToU[MU], root; int AL[MU], AR[MU], BL[MU], BR[MU]; int S[MX], lson[MX], rson[MX], sz; void DFS(int u, int f, int flag) { (!flag ? AL[u] : BL[u]) = ++dfn; for(int i = Head[u]; ~i; i = E[i].nxt) { int v = E[i].v; if(v == f) continue; DFS(v, u, flag); } (!flag ? AR[u] : BR[u]) = dfn; } void push_up(int rt) { S[rt] = S[lson[rt]] + S[rson[rt]]; } void Add(int &rt, int prt, int p, int l, int r) { rt = ++sz; S[rt] = S[prt]; lson[rt] = lson[prt]; rson[rt] = rson[prt]; if(l == r) { S[rt]++; return; } int m = (l + r) >> 1; if(p <= m) Add(lson[rt], lson[prt], p, l, m); else Add(rson[rt], rson[prt], p, m + 1, r); push_up(rt); } int Query(int lrt, int rrt, int L, int R, int l, int r) { if(L <= l && r <= R) return S[rrt] - S[lrt]; int m = (l + r) >> 1, ret = 0; if(L <= m) ret += Query(lson[lrt], lson[rrt], L, R, l, m); if(R > m) ret += Query(rson[lrt], rson[rrt], L, R, m + 1, r); return ret; } void Build() { for(int i = 1; i <= n; i++) ToU[BL[i]] = i; for(int i = 1; i <= n; i++) { Add(o[i], o[i - 1], AL[ToU[i]], 1, n); } } int main() { edge_init(); //FIN; scanf("%d", &n); for(int i = 1; i <= n - 1; i++) { int u, v; scanf("%d%d", &u, &v); OUT[v]++; edge_add(u, v); edge_add(v, u); } for(root = 1; OUT[root]; root++); dfn = 0; DFS(root, -1, 0); edge_init(); dfn = 0; for(int i = 1; i <= n; i++) OUT[i] = 0; for(int i = 1; i <= n - 1; i++) { int u, v; scanf("%d%d", &u, &v); OUT[v]++; edge_add(u, v); edge_add(v, u); } for(root = 1; OUT[root]; root++); DFS(root, -1, 1); Build(); LL ans = 0; for(int i = 1; i <= n; i++) { LL c = Query(o[BL[i] - 1], o[BR[i]], AL[i], AR[i], 1, n); ans += (c - 1) * (c - 2) / 2; } printf("%I64d\n", ans); return 0; }
实际上,主席树的思路虽然非常容易想到,但是代码量稍微大,常数稍微大。
其实这题还能不使用可持久结构,只需要使用最基础的树状数组,同样能完成该题。
总的思路和上面那种思路是一样的,我们的目的是求对每个节点的x。我们遍历第二颗树的时候,采用的是前序遍历。
首先我们在遍历子树前算出s=sum(r)-sum(l-1)的值,表示此时不包括子树时,树状数组中数字在[l,r]范围内的个数之和
然后再遍历子树,遍历完后,x=sum(r)-sum(l-1)-s,这就表示该子树的答案了!
#include <map> #include <set> #include <cmath> #include <ctime> #include <stack> #include <queue> #include <cstdio> #include <cctype> #include <bitset> #include <string> #include <vector> #include <cstring> #include <iostream> #include <algorithm> #include <functional> #define fuck(x) cout<<"["<<x<<"]"; #define FIN freopen("input.txt","r",stdin); #define FOUT freopen("output.txt","w+",stdout); #pragma comment(linker, "/STACK:102400000,102400000") using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef long long LL; const int MX = 1e5 + 5; struct Edge { int v, nxt; } E[MX * 2]; int Head[MX], erear; void edge_init() { erear = 0; memset(Head, -1, sizeof(Head)); } void edge_add(int u, int v) { E[erear].v = v; E[erear].nxt = Head[u]; Head[u] = erear++; } LL ans = 0; int n, dfn, OUT[MX], root; int AL[MX], AR[MX], sum[MX]; void add(int x) { for(; x <= n; x += x & -x) sum[x]++; } int ask(int x) { int ret = 0; for(; x; x -= x & -x) ret += sum[x]; return ret; } void read() { edge_init(); dfn = 0; for(int i = 1; i <= n; i++) OUT[i] = 0; for(int i = 1; i <= n - 1; i++) { int u, v; scanf("%d%d", &u, &v); OUT[v]++; edge_add(u, v); edge_add(v, u); } for(root = 1; OUT[root]; root++); } void DFS1(int u, int f) { AL[u] = ++dfn; for(int i = Head[u]; ~i; i = E[i].nxt) { int v = E[i].v; if(v == f) continue; DFS1(v, u); } AR[u] = dfn; } void DFS2(int u, int f) { int sum = ask(AR[u]) - ask(AL[u] - 1); add(AL[u]); for(int i = Head[u]; ~i; i = E[i].nxt) { int v = E[i].v; if(v == f) continue; DFS2(v, u); } LL c = ask(AR[u]) - ask(AL[u] - 1) - sum; ans += (c - 1) * (c - 2) / 2; } int main() { //FIN; scanf("%d", &n); read(); DFS1(root, -1); read(); DFS2(root, -1); printf("%I64d\n", ans); return 0; }