「CodeForces-191C」Fools and Roads(LCA+树上差分/树链剖分)

「CodeForces-191C」Fools and Roads
LCA+树上差分/树链剖分,求树边的染色次数


题意

给定一个$N$节点的树,以及树上的$k$条简单路径(端点用$u$,$v$表示),求树上的各条边被$k$条简单路径经过的总次数。

解法

LCA+树上差分

对于树上的简单路径$path(u,v)$,如果对于每一次查询,都对路径$path(u_i,v_i)$上经过的每一条边的权值+1,时间复杂度为$O(k*n)=1e10$,显然我们需要优化时间复杂度。

我们引入树上差分(树的前缀和)来优化查询。

树上差分

对于树,有如下两个性质:

  1. 任意两个节点之间有且只有一条路径;
  2. 根节点确定时,一个节点只有一个父亲节点。

由此,在自根节点向下进行DFS时,对于$path(u,v)$的其中任意一点$u’≠r$,若$u’$被访问,则$u’$的父亲节点一定被访问。

基于这样的性质,我们将树上的路径$path(u,v)$分割为两条链,有$r=LCA(u,v)$,路径$path(u,v)$覆盖的边为$u→r$,$r→v$,对两条链分别进行差分。

关于边的差分中,$r=LCA(u,v)​$不包含在内。因此考虑链$u→r​$,有cf[u]++,cf[r]​--;同样的,对于链$r→v​$,有cf[v]​++,cf[r]​--,即

1
2
3
4
5
6
7
for each(u,v)
{
r=LCA(u,v);
cf[r]-=2;
cf[u]++;
cf[v]++;
}

在完成所有$path$的查询后,自根节点进行$DFS$求树上每个点的前缀和,此时,对于树边$path(u,v)$,$cf[v]$即为此条边经过的总次数,即,对于树上的每条边$path(u,v)$,有

$$ans[path(u,v)]=cf[v]$$

时间复杂度为$O(n)$

如,对于样例1

pic1

对每一组查询$path$求LCA后有

pic3

DFS求前缀和,即为所求解

pic2

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int maxn=1e5+10;

int head[maxn],pre[maxn],ans[maxn],cnt=0;
bool vis[maxn]={false};

vector<int> query[maxn];
int cf[maxn]={0};

struct Edge
{
int nex,to,no;
}edge[2*maxn];

void add(int u,int v,int no)
{
edge[cnt].nex=head[u];
edge[cnt].to=v;
edge[cnt].no=no;
head[u]=cnt++;
}

int Find(int x){return x==pre[x]?x:pre[x]=Find(pre[x]); }

void Union(int x,int y)
{
int a=Find(x),b=Find(y);
if(a!=b) pre[b]=a;
}

void Tarjan(int u,int fa)
{
for(int i=head[u];~i;i=edge[i].nex)
{
int v=edge[i].to;
if(v==fa) continue;
Tarjan(v,u);
Union(u,v);
vis[v]=true;
}
for(int i=0;i<query[u].size();i++)
{
int e=query[u][i];
if(vis[e])
{
int root=Find(e);
cf[root]-=2;
cf[e]+=1;
cf[u]+=1;
}
}
}

int dfs(int u,int fa)
{
int res=0;
for(int i=head[u];~i;i=edge[i].nex)
{
int v=edge[i].to;
if(v==fa) continue;
cf[u]+=dfs(v,u);
ans[edge[i].no]+=cf[v];
}
return cf[u];
}

int main()
{
int n,u,v,k;
scanf("%d",&n);
memset(head,0xff,sizeof(head));
for(int i=1;i<=n;i++)
pre[i]=i;
for(int i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v,i);
add(v,u,i);
}
scanf("%d",&k);
while(k--)
{
scanf("%d%d",&u,&v);
query[u].push_back(v);
query[v].push_back(u);
}
Tarjan(1,0);
dfs(1,0);
for(int i=1;i<n;i++)
printf("%d ",ans[i]);
return 0;
}

树链剖分

(待补)