CF708C 【Centroids】树上DP

本文介绍了如何解决树上DP中的Centroids问题。首先通过简化问题,阐述了如何求解无根树中任意边的size,接着讨论了在允许修改树的情况下,如何处理边的增删以找到树的中心。通过两次DFS实现算法,并提供了相应的代码示例。

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

洛谷同步博客

1.我们先来简化一下问题

如果题目不让我们改造这棵树,就很好求了。
先求出 s i z e [ e ] size[e] size[e](如下图), e e e为无根树中的任意一条有向边(我们在邻接表中使用一对有向边 e e e e ‾ \overline{e} e来表示,其中 e ‾ \overline{e} e e e e的反向边,表示以 t a i l [ e ] tail[e] tail[e](表示边e的尾)为根的子树的大小。可知 s i z e [ e ‾ ] = n − s i z e [ e ] size[\overline{e}]=n-size[e] size[e]=nsize[e]
图一
要求出 s i z e [ e ] size[e] size[e] s i z e [ e ‾ ] size[\overline{e}] size[e],可以用一个 d f s dfs dfs来解决( e ‾ \overline{e} e可以用一个小技巧:e^1

上代码:

void dfs(int u,int e)
{
    size[e]=1;
    for(int i=0;i<(int)G[u].size();i++)
        if(G[u][i]!=(e^1))
        {
            int v=edges[G[u][i]].v;
            dfs1(v,G[u][i]);
            add(u,G[u][i]);
            size[e]+=size[G[u][i]];
        }
    size[e^1]=n-size[e];
}

这样,就可以了:把每个节点的各个子节点的 s i z e size size值扫描一遍,有 s i z e [ v ] ≥ n 2 ( v ∈ s o n [ u ] ) size[v]≥\frac{n}{2}(v\in son[u]) size[v]2n(vson[u])就说明 u u u不是树的中心。

2.说了半天,回到原问题(还有啊! q w q qwq qwq!)

做好迎接挑战的准备吧!

我们要把一条边删去,并连上另一条新边,也就是说,要把一个子树切割下来,在把他连到新的节点上。
我们可以计算一个 c [ e ] c[e] c[e] e e e的定义同 s i z e [ e ] size[e] size[e]),表示以 t a i l [ e ] tail[e] tail[e]为根的子树中最多可以割下多少个节点,可知 c [ e ] ≤ n 2 c[e] \le \frac{n}{2} c[e]2n
可以在原来的 d f s dfs dfs的基础上再加一个 d f s dfs dfs(并用一个数据结构来辅助实现):

void add(int u,int i)
{
    if(best[u]<=c[i])
    {
        sec[u]=best[u];
        best[u]=c[i];
        idx[u]=i;
    }
    else sec[u]=max(sec[u],c[i]);
}
int query(int u,int i)
{
    if(i!=idx[u]) return best[u];
    else return sec[u];
}
void dfs1(int u,int e)
{
    size[e]=1;
    c[e]=0;
    for(int i=0;i<(int)G[u].size();i++)
        if(G[u][i]!=(e^1))
        {
            int v=edges[G[u][i]].v;
            dfs1(v,G[u][i]);
            add(u,G[u][i]);
            size[e]+=size[G[u][i]];
            c[e]=max(c[e],c[G[u][i]]);
        }
    size[e^1]=n-size[e];
    if(size[e]<=n/2) c[e]=size[e];
}
void dfs2(int u,int e)
{
    add(u,e^1);
    for(int i=0;i<(int)G[u].size();i++)
        if(G[u][i]!=(e^1))
        {
            if(size[G[u][i]^1]<=n/2) c[G[u][i]^1]=size[G[u][i]^1];
            else c[G[u][i]^1]=query(u,G[u][i]);
            dfs2(edges[G[u][i]].v,G[u][i]);
        }
}

3.于是我们就大功告成了

献上我丑陋的代码:

#include<iostream>
#include<vector> 
using namespace std;
const int maxn=800005;
struct Edge
{
    int u,v;
    Edge(int x,int y): u(x),v(y){}
};
int n,size[maxn],c[maxn],best[maxn],sec[maxn],idx[maxn];
vector<Edge> edges;
vector<int> G[maxn];
void add(int u,int i)
{
    if(best[u]<=c[i])
    {
        sec[u]=best[u];
        best[u]=c[i];
        idx[u]=i;
    }
    else sec[u]=max(sec[u],c[i]);
}
int query(int u,int i)
{
    if(i!=idx[u]) return best[u];
    else return sec[u];
}
void dfs1(int u,int e)
{
    size[e]=1;
    c[e]=0;
    for(int i=0;i<(int)G[u].size();i++)
        if(G[u][i]!=(e^1))
        {
            int v=edges[G[u][i]].v;
            dfs1(v,G[u][i]);
            add(u,G[u][i]);
            size[e]+=size[G[u][i]];
            c[e]=max(c[e],c[G[u][i]]);
        }
    size[e^1]=n-size[e];
    if(size[e]<=n/2) c[e]=size[e];
}
void dfs2(int u,int e)
{
    add(u,e^1);
    for(int i=0;i<(int)G[u].size();i++)
        if(G[u][i]!=(e^1))
        {
            if(size[G[u][i]^1]<=n/2) c[G[u][i]^1]=size[G[u][i]^1];
            else c[G[u][i]^1]=query(u,G[u][i]);
            dfs2(edges[G[u][i]].v,G[u][i]);
        }
}
int main()
{
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        edges.push_back(Edge(u,v));
        edges.push_back(Edge(v,u));
        G[u].push_back(edges.size()-2);
        G[v].push_back(edges.size()-1);
    }
    dfs1(1,edges.size());
    dfs2(1,edges.size());
    for(int i=1;i<=n;i++)
    {
        bool ok=true;
        for(int j=0;j<(int)G[i].size();j++)
            if(size[G[i][j]]-c[G[i][j]]>n/2)
            {
                ok=false;
                break;
            }
        cout<<ok<<' ';
    }
    return 0;
}

P . S . : P.S.: P.S.:如有不当,请指出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值