路径计数转成二维数点很妙啊
题目描述
NiroBC 姐姐是个活泼的少女,她十分喜欢爬树,而她家门口正好有一棵果树,正好满足了她爬树的需求。
这颗果树有 $N$ 个节点,标号 $1 ldots N$ 。每个节点长着一个果子,第 $i$ 个节点上的果子颜色为 $C_i$ 。
NiroBC 姐姐每天都要爬树,每天都要选择一条有趣的路径 $(u, v)$ 来爬。
一条路径被称作有趣的,当且仅当这条路径上的果子的颜色互不相同。
$(u, v)$ 和 $(v, u)$ 被视作同一条路径。特殊地, $(i, i)$ 也被视作一条路径,这条路径只含 $i$ 一个节点,显然是有趣的。
NiroBC 姐姐想知道这颗树上有多少条有趣的路径。
输入格式
第一行,一个整数 $N$ ,表示果树的节点数目。
接下来一行 $N$ 个整数 $C_{1 ldots N}$ ,表示 $N$ 个果子各自的颜色。
再接下来 $N-1$ 行,每行两个整数 $u_i, v_i$ ,表示 $u_i$ 和 $v_i$ 之间有一条边。
数据保证这 $N-1$ 条边构成一棵树。
输出格式
一个整数,有趣的路径的数量。
样例
样例输入 1
3
1 2 3
1 2
1 3
样例输出 1
6
样例输入 2
5
1 1 2 3 3
1 2
1 3
2 4
2 5
样例输出 2
8
样例解释 1
有 $(1,1)(1,2)(1,3)(2,2)(2,3)(3,3)$ 共 $6$ 条路径。
样例解释 2
有 $(1,1)(1,3)(2,2)(2,4)(2,5)(3,3)(4,4)(5,5)$ 共 $8$ 条路径。
数据范围与提示
对于所有数据, $1 le N le 10^5$ ,$1 le C_i le N$ ,每种颜色在树上出现不超过 $20$ 次。
本题采用打包测试。
各个 Subtask 的特殊限制如下,不填代表该项无特殊限制。
Subtask 编号 | $N$ | 其他限制 | 该 Subtask 分值 |
---|---|---|---|
0 | $le 100$ | 12 | |
1 | $le 3000$ | 25 | |
2 | 整棵树形成一条依次为 $1, 2, 3, ldots , N$ 的链 | 30 | |
3 | 33 |
题目分析
二维数点
注意到每种颜色的出现次数非常小,于是考虑枚举同种颜色。
对于颜色相同的一对点,它们会把树分成三个部分,而两端部分不能够相互连边。这个部分用dfs序把图再构一遍,就方便处理了。至于两种分别是成链不成链的情况,稍微细心点细节就没问题。
点对$(x,y)$可以表示为二维点$(x,y)$,也就是说每一次将非法位置转为二维矩形覆盖,那么最后就是枚举每一个点,对非法矩形差分扫描线处理。
码力还是不够,标记${ m //HERE}$都是打挂的地方。
1 #include<bits/stdc++.h> 2 const int maxn = 100035; 3 const int maxm = 200035; 4 const int maxLog = 23; 5 6 struct dfn 7 { 8 int l,r; 9 }a[maxn]; 10 struct node 11 { 12 int mn,tot,tag; 13 }f[maxn<<2]; 14 struct line 15 { 16 int l,r,opt; 17 line (int a=0, int b=0, int c=0):l(a),r(b),opt(c) {} 18 }; 19 long long ans; 20 int n,c[maxn],fa[maxn][maxLog],dep[maxn],chTot; 21 int edgeTot,head[maxn],nxt[maxm],edges[maxm]; 22 std::vector<int> col[maxn]; 23 std::vector<line> mdf[maxn]; 24 25 int read() 26 { 27 char ch = getchar(); 28 int num = 0, fl = 1; 29 for (; !isdigit(ch); ch=getchar()) 30 if (ch=='-') fl = -1; 31 for (; isdigit(ch); ch=getchar()) 32 num = (num<<1)+(num<<3)+ch-48; 33 return num*fl; 34 } 35 void addedge(int u, int v) 36 { 37 edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot; 38 edges[++edgeTot] = u, nxt[edgeTot] = head[v], head[v] = edgeTot; 39 } 40 inline int kfa(int x, int d) 41 { 42 if (d==-1) return x; 43 for (int i=20; i>=0; i--) 44 if (d>>i&1) x = fa[x][i]; 45 return x; 46 } 47 void addLine(int l1, int r1, int l2, int r2) 48 { 49 if (l1 > r1||l2 > r2) return; 50 mdf[l1].push_back(line(l2, r2, 1)); 51 mdf[r1+1].push_back(line(l2, r2, -1)); 52 mdf[l2].push_back(line(l1, r1, 1)); 53 mdf[r2+1].push_back(line(l1, r1, -1)); 54 } 55 inline void split(int x, int y) 56 { 57 if (dep[x] > dep[y]) std::swap(x, y); 58 int anc = kfa(y, dep[y]-dep[x]-1); //HERE 59 if (fa[anc][0]!=x||(dep[x]==dep[y]&&x!=y)) addLine(a[x].l, a[x].r, a[y].l, a[y].r); 60 else{ 61 addLine(1, a[anc].l-1, a[y].l, a[y].r); 62 addLine(a[anc].r+1, n, a[y].l, a[y].r); //HERE 这里打挂只剩链30pts 63 } 64 } 65 void smcolConnect() 66 { 67 register int i,j,k; 68 for (i=1; i<=n; i++) 69 for (j=0; j<col[i].size(); j++) 70 for (k=j+1; k<col[i].size(); k++) 71 split(col[i][j], col[i][k]); //HERE 72 } 73 void dfs(int x, int fat) 74 { 75 dep[x] = dep[fat]+1, fa[x][0] = fat; 76 a[x].l = ++chTot; 77 for (int i=head[x]; i!=-1; i=nxt[i]) 78 if (edges[i]!=fat) dfs(edges[i], x); 79 a[x].r = chTot; 80 } 81 void build(int rt, int l, int r) 82 { 83 f[rt].tot = r-l+1; 84 if (l==r) return; 85 int mid = (l+r)>>1; 86 build(rt<<1, l, mid); 87 build(rt<<1|1, mid+1, r); 88 } 89 void init() 90 { 91 for (int j=1; j<=20; j++) 92 for (int i=1; i<=n; i++) 93 fa[i][j] = fa[fa[i][j-1]][j-1]; 94 build(1, 1, n); 95 } 96 void pushdown(int rt) 97 { 98 int l = rt<<1, r = rt<<1|1, &t = f[rt].tag; 99 if (t){ 100 f[l].tag += t, f[r].tag += t; 101 f[l].mn += t, f[r].mn += t, t = 0; //HERE 102 } 103 } 104 void pushup(int rt) 105 { 106 f[rt].mn = std::min(f[rt<<1].mn, f[rt<<1|1].mn); 107 f[rt].tot = (f[rt].mn==f[rt<<1].mn?f[rt<<1].tot:0)+(f[rt].mn==f[rt<<1|1].mn?f[rt<<1|1].tot:0); 108 } 109 void modify(int rt, int L, int R, int l, int r, int c) 110 { 111 if (L <= l&&r <= R){ 112 f[rt].mn += c, f[rt].tag += c; 113 return; 114 } 115 int mid = (l+r)>>1; 116 pushdown(rt); //HERE 117 if (L <= mid) modify(rt<<1, L, R, l, mid, c); 118 if (R > mid) modify(rt<<1|1, L, R, mid+1, r, c); 119 pushup(rt); 120 } 121 int main() 122 { 123 memset(head, -1, sizeof head); 124 n = read(); 125 for (int i=1; i<=n; i++) c[i] = read(), col[c[i]].push_back(i); 126 for (int i=1; i<n; i++) addedge(read(), read()); 127 dfs(1, 0), init(); 128 smcolConnect(); 129 for (int i=1; i<=n; i++) 130 { 131 for (unsigned int p=0; p<mdf[i].size(); p++) 132 modify(1, mdf[i][p].l, mdf[i][p].r, 1, n, mdf[i][p].opt); 133 if (f[1].mn==0) ans += f[1].tot; 134 } 135 printf("%lld ",(ans+n)>>1); 136 return 0; 137 }
DSU做法
还看到一种神奇的dsu+可持久化线段树做法:LibreOJ #6276.果树 dsu on tree+可持久化线段树
END