下笔春蚕食叶声。

学习笔记:虚树

有没有种可能,马上就要上考场了,不应该学新的东西(?)
算了,not important。
《不 应 该 学 新 的 东 西》
是谁的模拟赛有动态虚树?

板子

在某些情况下,我们要考虑的只有关键节点和他们的lca们组成的树。
实现:
先dfs,记录dfn和dep。然后把关键点按照dfn排序一个一个加入。
开一个栈,存的是准备塞进虚树但是还没有塞的(虚树上从根到stack[top]的路径上的点)。
假如准备加入新关键点v,那么分类讨论

  • lca(stack[top],v)=stack[top] v直接进栈
  • lca(stack[top],v)!=stack[top]
    说明stack[top]在虚树上的子树已经全部被遍历过,可以拿去构建虚树,退出他们并加进虚树。
    之后v进栈
void dfs(int u, int fa) {
    dfn[u] = ++tim; dep[u] = dep[fa] + 1; mxdep = max(mxdep, dep[u]);
    vec[dep[u]].push_back(u);
    for(int i = hd[u]; i; i = nxt[i]) {
        int v = to[i]; if(v == fa) continue;
        f[v][0] = u; 
        for(int j = 1; j <= lim; j++) 
            f[v][j] = f[f[v][j - 1]][j - 1];
        dfs(v, u);
    }
}
ll solve() {
    top = 0;
    stk[++top] = 1; G[1].clear();
    for(int i = 0; i < vec[d].size(); i++) { //这是Color the tree的代码。
        int v = vec[d][i], lca = getlca(stk[top], v);
        G[v].clear();
        if(lca == stk[top]) stk[++top] = v; //不要写错
        else {
            while(top && dfn[stk[top - 1]] >= dfn[lca]) {//不要写错
                G[stk[top - 1]].push_back(stk[top]);
                top--;
            }
            if(lca != stk[top]) {
                G[lca].clear();
                G[lca].push_back(stk[top]);
                stk[top] = lca;
            }
            stk[++top] = v;
        }
    }
    while(top > 1) {
        G[stk[top - 1]].push_back(stk[top]);
        top--;
    }
    return DP(1, 0);
}

题睦

Color the Tree

我写ucup被虚树打爆了,所以这是我的虚树入门题!!1
题意:
你有一棵全白的树。
有n种操作。第i种操作能以 \(a_i\) 的代价涂黑距【你在这次操作时选择的点u】为i且是u的子孙节点的点v们。每种操作可以用多次。
题解:
实际上不同深度的点相互之间并不影响,那么我们分开考虑每一层的点。
考虑处理深度为 \(d\) 的层:
\(dp_u\) 为涂黑u子树内在主树上深度为d的点的最小代价

\[dp_u=min(a_{d-{dep}_u},\sum_{v\in son(u)} dp_v) \]

这是 \(O(n^2)\) 的,T飞啦。
实际上我们要考虑的只有叶子节点和他们的lca们组成的树,你们这是个什么树阿,你们这原来是个虚树。

Warn:总该不会有人学了五年OI把0开始下标当成1开始下标,WA on 2然后心态爆炸几小时吧?

Kingdom and its Cities

你发现 \(\sum k\) 很小
你掏出一个虚树板子。
这是你的dp部分,你懒得解释。
你由于虚树两个地方写错,dp写错挂飞了。

PII DP(int u, int fa) { //?
    //cout<<"DP: "<<u<<" "<<fa<<endl;
    int ret = 0, qwq = 0;
    for(int i = 0; i < G[u].size(); i++) {
        int v = G[u][i];
        PII tmp = DP(v, u);
        ret += tmp.se;
        qwq += (tmp.fi == 1); 
    }
    if(zy[u]) {
        ret += qwq;
        return mkp(1, ret);
    } else {
        if(qwq == 0) return mkp(0, ret); //有的人就是直接忽略这种情况。啸了
        else if(qwq == 1)
            return mkp(1, ret);
        else return mkp(0, ret + 1);
    }
}

P3233 [HNOI2014]世界树

代码实现有点烦,但小样例很强,一遍过了
看到 $\sum $ 那么我们知道它是虚树,但是,这 并 不 是 难 点

  • 考虑虚树上某个点究竟被谁占领,只要 \(O(n)\) dfs两次计算各个点到哪个议事处最近就好了
  • 考虑非虚数上的点,也就是藏在边上的(为了实现方便我们把1加进虚树,让每个点都在虚树边上/虚树边上的点或端点相连的被隐去的子树)。
    • 考虑虚树边的两端:买虚树上的点送它连着的不在虚树上的子树。
    • 考虑虚树边上子树的点/虚树边上的点:
      如果颜色相同:这些点都是该颜色的
      如果颜色不同:中间一定有个分界点可以分开两种颜色的领地
      倍增维护一下就好。《一 下 就 好》
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mkp make_pair
#define ls(x) ((x)<<1)
#define rs(x) ((x)<<1|1)
#define fi first
#define se second
#define PII pair<int,int>
const int N = 3e5 + 10, lim = 19, inf = 0x3f3f3f3f;
int top, n, tim, dfn[N], dep[N], f[N][25], sz[N];
int k, e, to[N << 1], nxt[N << 1], hd[N], d, stk[N];
int gj[N], res[N], isgj[N];
PII dist[N];
vector<int>G[N];
void add(int u, int v) { to[++e] = v; nxt[e] = hd[u]; hd[u] = e; }
void dfs1(int u, int fa) {
    sz[u] = 1;
    dfn[u] = ++tim; dep[u] = dep[fa] + 1;
    for(int i = hd[u]; i; i = nxt[i]) {
        int v = to[i]; if(v == fa) continue;
        f[v][0] = u; 
        for(int j = 1; j <= lim; j++) 
            f[v][j] = f[f[v][j - 1]][j - 1];
        dfs1(v, u);
        sz[u] += sz[v];
    }
}
int getlca(int x, int y) {
    if(dep[x] < dep[y]) swap(x, y);
    for(int i = lim; i >= 0; i--)
        if(dep[f[x][i]] >= dep[y])
            x = f[x][i];
    if(x == y) return x;
    for(int i = lim; i >= 0; i--)
        if(f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}
int getdis(int x, int y) {
    int t = getlca(x, y);
    return dep[x] + dep[y] - 2 * dep[t];
}
void build() {
    top = 0;
    stk[++top] = 1; G[1].clear();
    for(int i = 1; i <= k; i++) {
        int v = gj[i]; G[v].clear(); 
        if(v == 1) continue;
        int lca = getlca(stk[top], v);
        if(lca == stk[top]) stk[++top] = v;
        else {
            while(top > 1 && dep[stk[top - 1]] >= dep[lca]) {
                G[stk[top - 1]].push_back(stk[top]);
            //    cout<<stk[top - 1]<<" "<<stk[top]<<endl;
                top--;
            }
            if(stk[top] != lca)  {
                G[lca].clear();
                G[lca].push_back(stk[top]); 
            //   cout<<lca<<" "<<stk[top]<<endl;
                stk[top] = lca; 
            }
            stk[++top] = v;
        }
    }
    while(top > 1) {
        G[stk[top - 1]].push_back(stk[top]); 
        //cout<<stk[top - 1]<<" "<<stk[top]<<endl;
        top--;
    }
    return;
}
bool cmp(int a, int b) { return dfn[a] < dfn[b]; }
int find(int u, int v) {
    for(int i = lim; i >= 0; i--)
        if(dep[f[v][i]] > dep[u])
            v = f[v][i];
    return v;
}
void dfs_son_to_u(int u, int fa) {
    if(isgj[u]) dist[u] = mkp(u, 0);
    else dist[u] = mkp(0, inf);
    for(int i = 0; i < (int)G[u].size(); i++) {
        int v = G[u][i];
        dfs_son_to_u(v, u);
        if(dist[u].se > dist[v].se + dep[v] - dep[u] || dist[u].se == dist[v].se + dep[v] - dep[u] && dist[v].fi < dist[u].fi)
            dist[u] = mkp(dist[v].fi, dist[v].se + dep[v] - dep[u]);
    }
}
void dfs_fa_to_u(int u, int fa) {
    int col = dist[u].fi;
    int tmp = sz[u];
    for(int i = 0; i < (int)G[u].size(); i++) {
        int v = G[u][i]; tmp -= sz[find(u, v)];
        if(dist[u].se + dep[v] - dep[u] < dist[v].se || dist[u].se + dep[v] - dep[u] == dist[v].se && dist[u].fi < dist[v].fi)
            dist[v] = mkp(dist[u].fi, dist[u].se + dep[v] - dep[u]);
        dfs_fa_to_u(v, u);
    }
  //  cout<<u<<" "<<col<<"+="<<tmp<<endl;
    res[isgj[col]] += tmp;
}
void dfs2(int u, int fa) {
    //cout<<"dfs2: "<<u<<" "<<fa<<endl;
    //怎么算虚树上某条边上挂出去的子树,,什么破防瞬间,,
    if(fa != 0) {
        if(dist[u].fi == dist[fa].fi) {
            int t = find(fa, u);
            if(dep[t] <= dep[u]) res[isgj[dist[u].fi]] += sz[t] - sz[u];//, cout<<sz[t] - sz[u]<<endl;
          //  cout<<t<<" "<<u<<endl;
        } else {
            int colu = dist[u].fi, colfa = dist[fa].fi;
          //  cout<<colu<<"&"<<colfa<<endl;
            int t = u;
            if(dist[u].fi < dist[fa].fi) {
                for(int i = lim; i >= 0; i--)
                    if(f[t][i] && dep[f[t][i]] >= dep[fa] && getdis(colu, f[t][i]) <= getdis(colfa, f[t][i]))
                        t = f[t][i];
            } else {
                for(int i = lim; i >= 0; i--)
                    if(f[t][i] && dep[f[t][i]] >= dep[fa] && getdis(colu, f[t][i]) < getdis(colfa, f[t][i]))
                        t = f[t][i];
            }
            if(dep[t] <= dep[u]) res[isgj[colu]] += sz[t] - sz[u];//, cout<<t<<" "<<u<<endl, cout<<sz[t] - sz[u]<<endl;
           
            int ze = find(fa, u);
            if(dep[ze] <= dep[f[t][0]]) res[isgj[colfa]] += sz[ze] - sz[t];//, cout<<ze<<" "<<t<<endl, cout<<sz[ze] - sz[t]<<endl;
        }
    }
    for(int i = 0; i < (int)G[u].size(); i++) {
        int v = G[u][i];
        dfs2(v, u);
    }
}
void doit() {
    //cout<<"<============ doit ============>"<<endl;
    scanf("%d", &k);
    for(int i = 1; i <= k; i++)
        scanf("%d", &gj[i]), isgj[gj[i]] = i;
    sort(gj + 1, gj + k + 1, cmp);
    build();
    dfs_son_to_u(1, 0); dfs_fa_to_u(1, 0);
    dfs2(1, 0);
    for(int i = 1; i <= k; i++)
        printf("%d ", res[i]);
    puts("");
    for(int i = 1; i <= k; i++)
        isgj[gj[i]] = 0, res[i] = 0;
}
int main() {
    //freopen("ex.in", "r", stdin);
    //freopen("ex.out", "w", stdout);
    scanf("%d", &n);
    for(int i = 1, u, v; i < n; i++) {
        scanf("%d%d", &u, &v);
        add(u, v), add(v, u);
    }
    dfs1(1, 0);

    int q; scanf("%d", &q);
    while(q--) doit();
    return 0;
}

摆了,我觉得够了,不想写了。

posted @ 2023-03-27 16:46  ACwisher  阅读(140)  评论(2编辑  收藏  举报