[树链剖分]List wants to travel,Relief grain,hotel加强版,This world need more Zhu

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 dj
在这里插入图片描述
树形 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][j1]

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][j1]:在 u u u已处理过的儿子中,两点距离 l c a lca lca d d d l c a lca lca距离 u u u d − j d-j dj的点对数为 g [ u ] [ j ] g[u][j] g[u][j],能与现在处理的儿子 v v v匹配的点个数则为 f [ v ] [ j − 1 ] f[v][j-1] f[v][j1] f [ v ] [ j − 1 ] f[v][j-1] f[v][j1]的点距离 v v v j − 1 j-1 j1,距离 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][j1]+=g[v][j]

g [ u ] [ j − 1 ] + = g [ v ] [ j ] g[u][j-1]+=g[v][j] g[u][j1]+=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 dj的点对数,这些点对数的 l c a lca lca距离 u u u则为 d − j + 1 d-j+1 dj+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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值