【线段树 扫描线 二维数点】loj#6276. 果树

路径计数转成二维数点很妙啊

题目描述

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)$,也就是说每一次将非法位置转为二维矩形覆盖,那么最后就是枚举每一个点,对非法矩形差分扫描线处理。

码力还是不够,标记${\rm //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\n",(ans+n)>>1);
136     return 0;
137 }

 

DSU做法

还看到一种神奇的dsu+可持久化线段树做法:LibreOJ #6276.果树 dsu on tree+可持久化线段树

 

 

 

END

posted @ 2018-11-07 19:49  AntiQuality  阅读(585)  评论(0编辑  收藏  举报