B:Relief grain
将一段区间修改的标记变成差分,每次都是连续一段的 d f n dfn dfn序修改
从小到大枚举 d f n dfn dfn,在一段标记的最开头的 d f n dfn dfn插入,最末尾的 d f n dfn dfn再 + 1 +1 +1删除
就保证了在这连续的一段都出现了
此时仍然存在于线段树中的标记就一定是经过 d f n dfn dfn序对应回原树的节点
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define maxn 100005
#define MAX 100000
vector < int > G[maxn], ins[maxn], del[maxn];
int n, m, cnt;
int siz[maxn], son[maxn], f[maxn], dep[maxn];
int dfn[maxn], top[maxn], rnk[maxn];
int t[maxn << 2], id[maxn << 2], ans[maxn];
void dfs1( int u, int fa ) {
son[u] = 0, siz[u] = 1, dep[u] = dep[fa] + 1, f[u] = fa;
for( int i = 0;i < G[u].size();i ++ ) {
int v = G[u][i];
if( v == fa ) continue;
dfs1( v, u );
siz[u] += siz[v];
if( ! son[u] || siz[v] > siz[son[u]] )
son[u] = v;
}
}
void dfs2( int u, int t ) {
top[u] = t, dfn[u] = ++ cnt, rnk[cnt] = u;
if( ! son[u] ) return;
dfs2( son[u], t );
for( int i = 0;i < G[u].size();i ++ ) {
int v = G[u][i];
if( v == f[u] || v == son[u] ) continue;
else dfs2( v, v );
}
}
void pushup( int num ) {
if( t[num << 1] >= t[num << 1 | 1] ) t[num] = t[num << 1], id[num] = id[num << 1];
else t[num] = t[num << 1 | 1], id[num] = id[num << 1 | 1];
}
void build( int num, int l, int r ) {
t[num] = id[num] = 0;
if( l == r ) {
id[num] = l;
return;
}
int mid = ( l + r ) >> 1;
build( num << 1, l, mid );
build( num << 1 | 1, mid + 1, r );
}
void modify( int num, int l, int r, int pos, int v ) {
if( l == r ) {
t[num] += v;
return;
}
int mid = ( l + r ) >> 1;
if( pos <= mid ) modify( num << 1, l, mid, pos, v );
else modify( num << 1 | 1, mid + 1, r, pos, v );
pushup( num );
}
void solve( int u, int v, int w ) {
int fu = top[u], fv = top[v];
while( fu != fv ) {
if( dep[fu] < dep[fv] ) swap( fu, fv ), swap( u, v );
ins[dfn[fu]].push_back( w );
del[dfn[u] + 1].push_back( w );
u = f[fu], fu = top[u];
}
if( dep[u] < dep[v] ) swap( u, v );
ins[dfn[v]].push_back( w );
del[dfn[u] + 1].push_back( w );
}
int main() {
while( ~ scanf( "%d %d", &n, &m ) ) {
if( ! n && ! m ) return 0;
for( int i = 1;i <= n;i ++ ) G[i].clear(), ins[i].clear(), del[i].clear();
cnt = 0;
for( int i = 1, u, v;i < n;i ++ ) {
scanf( "%d %d", &u, &v );
G[u].push_back( v );
G[v].push_back( u );
}
dfs1( 1, 0 );
dfs2( 1, 1 );
for( int i = 1, x, y, z;i <= m;i ++ ) {
scanf( "%d %d %d", &x, &y, &z );
solve( x, y, z );
}
build( 1, 1, MAX );
for( int i = 1;i <= n;i ++ ) {
for( int j = 0;j < ins[i].size();j ++ )
modify( 1, 1, MAX, ins[i][j], 1 );
for( int j = 0;j < del[i].size();j ++ )
modify( 1, 1, MAX, del[i][j], -1 );
if( t[1] ) ans[rnk[i]] = id[1];
else ans[rnk[i]] = 0;
}
for( int i = 1;i <= n;i ++ )
printf( "%d\n", ans[i] );
}
return 0;
}
C:hotel加强版
f [ u ] [ j ] f[u][j] f[u][j]表示 u u u子树内距离 u u u为 j j j的点个数
g
[
u
]
[
j
]
g[u][j]
g[u][j]表示
u
u
u子树内存在两个点距离它们的
l
c
a
lca
lca为
d
d
d,且
l
c
a
lca
lca距离
u
u
u为
d
−
j
d-j
d−j
树形
D
P
DP
DP,是一个一个儿子暴力计算贡献的
a n s + = f [ u ] [ j ] × g [ v ] [ j + 1 ] + g [ u ] [ j ] × f [ v ] [ j − 1 ] ans+=f[u][j]\times g[v][j+1]+g[u][j]\times f[v][j-1] ans+=f[u][j]×g[v][j+1]+g[u][j]×f[v][j−1]
f
[
u
]
[
j
]
×
g
[
v
]
[
j
+
1
]
f[u][j]\times g[v][j+1]
f[u][j]×g[v][j+1]:在
u
u
u已处理过的儿子中距离
u
u
u为
j
j
j,那么距离现在处理的儿子
v
v
v的距离则为
j
+
1
j+1
j+1,所以是和以
v
v
v为根的子树内的距离
j
+
1
j+1
j+1点对进行匹配,即
g
[
v
]
[
j
+
1
]
g[v][j+1]
g[v][j+1]
g
[
u
]
[
j
]
×
f
[
v
]
[
j
−
1
]
g[u][j]\times f[v][j-1]
g[u][j]×f[v][j−1]:在
u
u
u已处理过的儿子中,两点距离
l
c
a
lca
lca为
d
d
d且
l
c
a
lca
lca距离
u
u
u为
d
−
j
d-j
d−j的点对数为
g
[
u
]
[
j
]
g[u][j]
g[u][j],能与现在处理的儿子
v
v
v匹配的点个数则为
f
[
v
]
[
j
−
1
]
f[v][j-1]
f[v][j−1],
f
[
v
]
[
j
−
1
]
f[v][j-1]
f[v][j−1]的点距离
v
v
v是
j
−
1
j-1
j−1,距离
u
u
u就是
j
j
j
g
[
u
]
[
j
+
1
]
+
=
f
[
u
]
[
j
+
1
]
×
f
[
v
]
[
j
]
g[u][j+1]+=f[u][j+1]\times f[v][j]
g[u][j+1]+=f[u][j+1]×f[v][j]:
f
[
u
]
[
j
+
1
]
,
f
[
v
]
[
j
]
f[u][j+1],f[v][j]
f[u][j+1],f[v][j]相互匹配的点对是很特殊的,因为这些点对的
l
c
a
lca
lca就是
u
u
u,这些点对距离
l
c
a
lca
lca为
j
+
1
j+1
j+1,
l
c
a
lca
lca距离
u
u
u的距离为
(
j
+
1
)
−
(
j
+
1
)
=
0
(j+1)-(j+1)=0
(j+1)−(j+1)=0,是符合
g
g
g的定义的
这种特殊的情况统计后,在 u u u往上跳的过程中, u u u不断降级为儿子,目前特殊的 l c a lca lca等于根的情况在以后就变成了下边的一般化的 g g g的转移,那个时候 u u u对根的贡献就是正确的了,类似于局部最优的这种思想,特殊情况往上跳就变成了一般情况
g [ u ] [ j − 1 ] + = g [ v ] [ j ] g[u][j-1]+=g[v][j] g[u][j−1]+=g[v][j]
g [ u ] [ j − 1 ] + = g [ v ] [ j ] g[u][j-1]+=g[v][j] g[u][j−1]+=g[v][j]: g [ v ] [ j ] g[v][j] g[v][j]是 v v v子树内 l c a lca lca距离 v v v为 d − j d-j d−j的点对数,这些点对数的 l c a lca lca距离 u u u则为 d − j + 1 d-j+1 d−j+1
f
[
u
]
[
j
+
1
]
+
=
f
[
v
]
[
j
]
f[u][j+1]+=f[v][j]
f[u][j+1]+=f[v][j]
f [ u ] [ j + 1 ] + = f [ v ] [ j ] f[u][j+1]+=f[v][j] f[u][j+1]+=f[v][j]:很好理解,距离 u u u为 j + 1 j+1 j+1的点个数加上距离当前正在处理的儿子 v v v距离为 j j j(距离 u u u就为 j + 1 j+1 j+1)的个数
长链剖分即可
长链剖分不同于重链剖分的无非是不再选用子树最大的儿子作为重儿子,而是选用长度最长(叶子节点深度最深)的儿子作为重儿子
用于处理只与深度有关的树上问题
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define maxn 100005
#define int long long
vector < int > G[maxn];
int n;
int dep[maxn], son[maxn];
int *f[maxn], *g[maxn], tmp[maxn << 2], *ip = tmp, ans;
void dfs1( int u, int fa ) {
for( int i = 0;i < G[u].size();i ++ ) {
int v = G[u][i];
if( v == fa ) continue;
dfs1( v, u );
dep[u] = max( dep[u], dep[v] );
if( dep[v] > dep[son[u]] ) son[u] = v;
}
dep[u] = dep[son[u]] + 1;
}
void dfs2( int u, int fa ) {
if( son[u] ) {
f[son[u]] = f[u] + 1;
g[son[u]] = g[u] - 1;
dfs2( son[u], u );
}
f[u][0] = 1; ans += g[u][0];
for( int i = 0;i < G[u].size();i ++ ) {
int v = G[u][i];
if( v == son[u] || v == fa ) continue;
f[v] = ip, ip += ( dep[v] << 1 );
g[v] = ip, ip += ( dep[v] << 1 );
dfs2( v, u );
for( int j = 0;j <= dep[v];j ++ ) {
ans += f[u][j] * g[v][j + 1];
if( j ) ans += g[u][j] * f[v][j - 1];
}
for( int j = 0;j <= dep[v];j ++ ) {
if( j ) g[u][j - 1] += g[v][j];
g[u][j + 1] += f[u][j + 1] * f[v][j];
f[u][j + 1] += f[v][j];
}
}
}
signed main() {
scanf( "%lld", &n );
for( int i = 1, u, v;i < n;i ++ ) {
scanf( "%lld %lld", &u, &v );
G[u].push_back( v );
G[v].push_back( u );
}
dfs1( 1, 0 );
f[1] = ip, ip += ( dep[1] << 1 );
g[1] = ip, ip += ( dep[1] << 1 );
dfs2( 1, 0 );
printf( "%lld\n", ans );
return 0;
}