[BZOJ3572][Hnoi2014]世界树 && 虚树+DP

本文介绍了一种基于深度优先搜索构建虚树的方法,并详细解释了如何通过这种虚树进行动态规划解决特定问题。文中提供了完整的C++代码实现,帮助读者深入理解算法细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这玩意好难啊Orz

完全不理解那个模拟深搜到底是什么鬼

果然像我这样的人最好早点滚粗 

要简历虚树 首先要选出虚树里面的点 那么关键点和关键点的LCA都要加入到虚树中来 那我们就深搜一遍 处理出每个节点的dfn值和儿子数 

按照dfn值依次枚举每一个关键节点 这样可以把同一棵子树内的节点一起找到

用一个深度单调的栈来维护树中的节点 每次取出一个关键点 求出它和栈顶元素的lca 

如果他们的lca的深度小于栈顶元素 lca的深度比栈中第二个元素大 即当前的lca卡在了两个树中的点之间 那么就由lca向当前的栈顶元素连边 无论是否连边 都直接把栈顶元素弹出 

如果你当前这次没有连边 说明你没有卡在栈顶两个元素之间 那么他们在之前就已经练好一条边了 可以直接pop掉

当栈顶元素的深度已经比lca小 那么就由栈顶元素向lca连边

这样就完成建立虚树的操作

然后就是DP了

我们首先用两边for循环处理掉虚树上某条边 两个端点的元素会走到同一个端点 即x 走向fa[x] 或者 fa[x] 走向 x 

然后我们枚举剩余的节点 它与父亲的边上的点不会都走向同一个点 那么我们求出他们距离的中点 中点以下的走向自己 中点以上的就走向父亲


果然还是好难啊QAQ


#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
#include<map>
#define SF scanf
#define PF printf
#define mp make_pair
#define fir first
#define sec second
#define bit(x) (1<<(x))
using namespace std;
typedef long long LL;
typedef pair <int, int> pii;
const int MAXN = 300000;
int n;
// Add edges
struct Node {
    int v, next;
} Edge[MAXN*2+10];
int adj[MAXN+10], ecnt;
void addedge(int u, int v) {
    Node &e = Edge[ecnt];
    e.v = v; e.next = adj[u]; adj[u] = ecnt++;
}
// DFS and build the tree
int dfn[MAXN+10], dcnt, f[MAXN+10][30], dep[MAXN+10], sz[MAXN+10];
void dfs(int u) {
    dfn[u] = ++dcnt;
    // prepare for LCA
    for(int i = 1; bit(i) <= dep[u]; i++) f[u][i] = f[f[u][i-1]][i-1];
     
    for(int i = adj[u]; ~i; i = Edge[i].next) {
        int v = Edge[i].v;
        if(v == f[u][0]) continue;
        f[v][0] = u; dep[v] = dep[u]+1;
        dfs(v);
        sz[u] += sz[v];
    }
    sz[u]++;
}
// Go to another point
int Go(int x, int k) {
	int del = dep[x] - k;
	for(int i = 20; i >= 0; i--)
		if(del & bit(i))
			x = f[x][i];
    return x;
}
// find LCA
int LCA(int x, int y) {
    if(dep[x] < dep[y]) swap(x, y);
    int del = dep[x] - dep[y];
    for(int i = 20; i >= 0; i--)
        if(del & bit(i))
            x = f[x][i];
    if(x == y) return x;
    for(int i = 20; i >= 0; i--)
        if(f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}
// solve the problem
int ask[MAXN+10], tree[MAXN+10], tmp[MAXN+10], tot, ans[MAXN+10];
int sta[MAXN+10], fa[MAXN+10], val[MAXN+10], in[MAXN+10];
pii near[MAXN+10];
bool cmp(int a, int b) { return dfn[a] < dfn[b]; }
void solve() {
    int m; SF("%d", &m);
    // Read the key points
    for(int i = 1; i <= m; i++) {
        int x; SF("%d", &x);
        ask[i] = tree[i] = tmp[i] = x;
        near[x] = mp(0, x); ans[x] = 0;
    }
    sort(ask+1, ask+1+m, cmp);
    int top = 0;
    tot = m;
    // find the new tree points
    for(int i = 1; i <= m; i++) {
        int cur = ask[i];
        if(!top) sta[++top] = cur, fa[cur] = 0;
        else {
            int lca = LCA(sta[top], cur);
            fa[cur] = lca;
            while(top && dep[sta[top]] > dep[lca]) {
                if(dep[sta[top-1]] <= dep[lca]) fa[sta[top]] = lca;
                top--;
            }
            if(sta[top] != lca) {
                fa[lca] = sta[top]; tree[++tot] = lca;
                sta[++top] = lca; near[lca] = mp(bit(30), 0);
            }
            sta[++top] = cur;
        }
    }
    sort(tree+1, tree+1+tot, cmp);
	// DP
    for(int i = 1; i <= tot; i++) {
        int x = tree[i];
        val[x] = sz[x];
        if(i > 1) in[x] = dep[x] - dep[fa[x]];
    }
    for(int i = tot; i > 1; i--) {
        int x = tree[i], pre = fa[x];
        near[pre] = min(near[pre], mp(near[x].fir + in[x], near[x].sec));
    }
    for(int i = 2; i <= tot; i++) {
        int x = tree[i], pre = fa[x];
        near[x] = min(near[x], mp(near[pre].fir + in[x], near[pre].sec));
    }
     
    for(int i = 1; i <= tot; i++) {
        int x = tree[i], pre = fa[x], sum = sz[Go(x, dep[pre]+1)] - sz[x];
        if(pre == 0) ans[near[x].sec] += n-sz[x];
        else {
            val[pre] -= sum + sz[x];
            if(near[x].sec == near[pre].sec) ans[near[x].sec] += sum;
            else {
                int dis = (dep[x] - dep[pre] - near[x].fir + near[pre].fir) >> 1;
                if(dis + near[x].fir == near[pre].fir + dep[x] - dep[pre] - dis && near[pre].sec < near[x].sec) dis--;
                int p = Go(x, dep[x] - dis);
                ans[near[x].sec] += sz[p] - sz[x];
                ans[near[pre].sec] += sum + sz[x] - sz[p];
            }
        }
    }
    for(int i = 1; i <= tot; i++) ans[near[tree[i]].sec] += val[tree[i]];
    for(int i = 1; i <= m; i++) PF("%d ", ans[tmp[i]]);
    puts("");
}
// Main
int main() {
    memset(adj, -1, sizeof(adj));
    SF("%d", &n);
    for(int i = 1; i < n; i++) {
        int u, v; SF("%d%d", &u, &v);
        addedge(u, v); addedge(v, u);
    }
    dfs(1);
    int _T; SF("%d", &_T); while(_T--) 
        solve();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值