El pueblo unido jamas serà vencido!

看懂但是不完全懂长链剖分

长链剖分

总体上和重链剖分挺像的.

首先定义重儿子为 : 子树深度最深的儿子.

然后剩下的是轻儿子.

连向重儿子的是重边,重边连成重链.

每个点都在唯一长链中,长链必然不相交

实现就和重链剖分差不多.

len 记录最深能达到的深度.


void dfs(int u,int _f){
	fe(i,u) {
	    int v = e[i].to;
		if (v != _f){
			dfs(v,u);
			if(len[v] > len[son[u]]) 
		        son[u] = v;
		}
	}
	len[u] = len[son[u]] + 1;
}

优化 DP

在维护 与深度有关 的信息时,从重儿子快速继承信息,然后暴力统计轻儿子信息,和 dsu on tree 很像.

这个 与深度有关 不是特别明确,其实就是下标和深度有关那种感觉,而不是其他什么玩意.

因为每个点仅属于一条长链,且轻儿子全部为长链顶,所以时间复杂度线性.

CF161D Distance in Tree

\(\texttt{Link.}\)

求一无根树内长度恰好为 \(k\) 的路径数量.

点分治是 \(\mathrm{O}(n\log n)\) 的,能不能更好一些 ?

考虑长链剖分优化朴素的树形 DP.

\(f_{i,j}\) 表示在 \(i\) 的子树到 \(i\) 距离为 \(j\) 的结点个数.

因为 \(k \le 500\) ,暴力转移然后乘法原理是可以通过本题的.

复杂度 \(\mathrm{O} (nk)\).

优化后复杂度为 \(\mathrm{O} (n)\).

为了方便的继承信息,用指针 ptr[i] 指向点 \(i\) 继承的信息.

这样也使得统计信息只使用一个数组,指针只表示某个结点信息的首个位置罢了.

然后这玩意因为暴力合并轻儿子的部分常数大,和我的树状数组 + 点分治一样快,离谱.

此题最优解居然是 dsu on tree,常数小就为所欲为吗

Code :

int head[N],ecnt = -1;
struct Edge {
    int nxt,to;
}e[N << 1];
inline void AddEdge(int st,int ed) {
    e[++ecnt] = (Edge) {head[st],ed},head[st] = ecnt;
    e[++ecnt] = (Edge) {head[ed],st},head[ed] = ecnt;
}

int k;

int ans;
int fa[N];
int len[N],hson[N];
int *ptr[N],buc[N];
int cnt;

void dfs1(int u,int _f) {
    fa[u] = _f;
    fe(i,u) {
        int v = e[i].to;
        if(v == _f) continue;
        dfs1(v,u);
        if(len[v] > len[hson[u]])
            hson[u] = v;
    }
    len[u] = len[hson[u]] + 1;
}

inline void Add(int u,int v) {
    ff(i,std::max(0,k - len[u]),std::min(len[v],k))
        ans += ptr[v][i] * ptr[u][k - i - 1];
    ff(i,0,len[v])
        ptr[u][i + 1] += ptr[v][i];
}

void dfs2(int u,int top) {
    if(u == top) {
        ptr[u] = buc + cnt;
        cnt += len[u];
    }
    else ptr[u] = ptr[fa[u]] + 1;
    fe(i,u) {
        int v = e[i].to;
        if(v == fa[u] || v == hson[u])
            continue;
        dfs2(v,v);
    }
    if(hson[u]) dfs2(hson[u],top);
    if(k < len[u]) ans += ptr[u][k];
    ++ptr[u][0];
    fe(i,u) {
        int v = e[i].to;
        if(v == fa[u] || v == hson[u])
            continue;
        Add(u,v);
    }
}

CF1009F Dominant Indices

似乎这题还是 dsu on tree 比较方便.

[POI2014] HOT-Hotels

此题和上面两题不太相同,不能用树上启发式合并.

\(\texttt{Link1.}\)

\(\texttt{Link2.}\)

对于没加强的,暴力 \(\mathrm{O} (n^2)\) DP 即可.

但是加强版就不行了.

然后设计一下状态 :

\(f_{i,j}\) : \(i\) 子树内到 \(i\) 距离为 \(j\) 的点数.

\(g_{i,j}\) : \(i\) 子树内到 \(i\) 距离均为 \(j\) 的点对数.

然后乘法原理.

好像讲得太草率了

看代码吧

我是不是得了 wind_whisper 综合征啊

Code :

int head[N],ecnt = -1;
struct Edge {
    int nxt,to;
}e[N << 1];
inline void AddEdge(int st,int ed) {
    e[++ecnt] = (Edge) {head[st],ed},head[st] = ecnt;
    e[++ecnt] = (Edge) {head[ed],st},head[ed] = ecnt;
}

int n;
int len[N],son[N];
ll tmp[N << 2];
ll *f[N],*g[N],*id = tmp;
ll ans;

void dfs(int u,int _f){
	fe(i,u) {
	    int v = e[i].to;
		if (v != _f){
			dfs(v,u);
			if(len[v] > len[son[u]]) 
		        son[u] = v;
		}
	}
	len[u] = len[son[u]] + 1;
}

void dp(int u,int _f){
	if(son[u]) {
	    f[son[u]] = f[u] + 1;
	    g[son[u]] = g[u] - 1;
	    dp(son[u],u);
	}
	f[u][0] = 1;
	ans += g[u][0];
	fe(i,u) {
		int v = e[i].to;
		if(v == _f || v == son[u]) continue;
		f[v] = id;
		id += len[v] << 1;
		g[v] = id;
		id += len[v] << 1;
		dp(v,u);
		ff(j,0,len[v] - 1){
			if(j) ans += f[u][j - 1] * g[v][j];
			ans += g[u][j + 1] * f[v][j];
		}
		ff(j,0,len[v] - 1){
			g[u][j + 1] += f[u][j + 1] * f[v][j];
			if(j) g[u][j - 1] += g[v][j];
			f[u][j + 1] += f[v][j];
		}
	}
}

int mian() {
    init_IO();
    mems(head,-1);
	n = read();
	ff(i,1,n - 1) AddEdge(read(),read());
	dfs(1,1);
	f[1] = id;
	id += len[1] << 1;
	g[1] = id;
	id += len[1] << 1;
	dp(1,0);
	write(ans);
	end_IO();
	return 0;
}

优化贪心

BZOJ 3252 攻略

\(\texttt{Link.}\)

题意概述 :

一棵 \(n\) 个点的有根树,点有点权,选出 \(k\) 条从根节点到叶结点的简单路径,使得这些路径的并的点权和最大.

求这个点权和.

然后你可以发现这个结构和长链剖分完的结构很想,长链也是不相交,于是不会多次统计贡献,长链也都到达叶子结点.

于是取权值大的就行,求 前 \(k\) 个,开个堆即可.

求 k 级祖先

\(\texttt{Link.}\)

给定一棵有根树,给出多个询问,形同 \(u,k\) 表示求点 \(u\) 向上跳 \(k\) 个结点的祖先.

首先直接树上倍增就是 \(<\mathrm{O}(n \log n),\mathrm{O} (\log n)>\) 的了.

试将其优化为 \(<\mathrm{O}(n \log n),\mathrm{O} (1)>\)

首先还是树上倍增,但是询问的时候只跳 \(k\) 二进制最高的那一位,就是跳 \(2^{\lfloor \log k \rfloor}\) 步.

链头预处理暴力在链上上下跳到达的点.

然后跳到所在长链的链头,靠预处理的信息求解即可.

\(k\) 级祖先找不到啥实际应用上的题啊... 一搜都是些 dsu on tree 和 树形DP 就能做的题...

希望以后能用上吧.

Code :

模板题直接给出了每个点的父亲,于是不需要加反边,dfs 的时候也没有传值传入父亲结点.

正常写的时候别忘了加反边啊.

以及我这个模板常数好大啊...


int head[N],ecnt = -1;
struct Edge {
    int nxt,to,w;
}e[N];
inline void AddEdge(int st,int ed) {
    e[++ecnt] = (Edge) {head[st],ed};
    head[st] = ecnt;
}

int dep[N];
int anc[N][20];
int hson[N],len[N],top[N];
std::vector<int> upp[N],low[N];
void dfs1(int u) {
    len[u] = dep[u] = dep[anc[u][0]] + 1;
    fe(i,u) {
        int v = e[i].to;
        anc[v][0] = u;
        for(int j = 0;anc[v][j];++j)
            anc[v][j + 1] = anc[anc[v][j]][j];
        dfs1(v);
        if(len[v] > len[u])
            len[u] = len[v],hson[u] = v;
    }   
}

void dfs2(int u,int t) {
    top[u] = t;
    if(u == t) {
        int p = u;
        ff(i,0,len[u] - dep[u])
            upp[u].push_back(p),p = anc[p][0];
        p = u;
        ff(i,0,len[u] - dep[u])
            low[u].push_back(p),p = hson[p];
    }
    if(hson[u]) dfs2(hson[u],t);
    fe(i,u) {
        int v = e[i].to;
        if(v == hson[u]) continue;
        dfs2(v,v);
    }
}

int l2[N];
inline int query(int u,int k) {
    if(!k) return u;
    u = anc[u][l2[k]];
    k -= (1 << l2[k]),k -= dep[u] - dep[top[u]];
    u = top[u];
    return k >= 0 ? upp[u][k] : low[u][-k];
}

posted @ 2022-01-28 15:44  AstatineAi  阅读(68)  评论(0编辑  收藏  举报