[HDU6065] RXD, tree and sequence

Problem Link

Description

    给定一棵有 n 个节点并且以1为根的树,根的深度为1。给定1 ~ n 的排列P,现在将其分成 k 个连续块,使得块内结点的LCA深度和最小,求这个最小值。

Solution

    首先我们要知道一个重要的性质:对于一个连续块,它最终的 LCA 一定可以由这个连续块内的相邻两个元素求 LCA 得出。这里简单证明一下:我们不妨设一个连续块的 LCA A (这里假定A不是连续块中的点,如果 A 是这个连续块中的点那结论就很显然了),那么这个连续块中的点一定分布在A的子树中,最重要的是包含这些点的子树至少有两棵(因为只有分布在不同子树中的点的 LCA 才会是 A )。接下来的证明就很简单了,很显然两个不在同一棵子树的点的LCA就是 A ,那么对于一个连续块,必定存在一对相邻的点所在的子树不同,我们只要找到这对相邻的点,就可以找到整个连续块的LCA了。除了这对相邻的点,这个连续块的点其他点都可以无视了(因为它们不会对答案有贡献),于是问题就很容易转化成了,求 K 对相邻的点的LCA的深度之和的最小值(有些同学可能要问了,一对相邻的点的 LCA 不一定会成为一个连续块的 LCA ,这个连续块中还可能存在其他相邻的点的 LCA 更优。 我们不必担心这个,因为我们求的是深度的最小值,所以如果存在更优的 LCA ,一定会被更新到)。另外,还可能存在只有一个点的连续块, 我们更新时要考虑这个。

    我们定义 dp[i][j] 表示遍历到第 i 个点时已经形成了j个连续块的最小 LCA 深度和(这不代表第 i 个点就在第j个连续块中,第 i 个点还可能未被归入一个连续块,而会与后面的点一起形成一个连续块)。遍历到i这个点时,有以下三种情况:

  • 留着 i 这个点,让它和后面的点形成一个连续块,即dp[i][j]=min(dp[i][j],dp[i1][j])
  • i 这个点独立形成一个连续块,即dp[i][j]=min(dp[i][j],dp[i1][j1]+dep[id[i]])
  • i i1 LCA 成为他们所在的连续块的 LCA (虽然这两个点的 LCA 不一定是他们所在的连续块的 LCA ,但是根据上面求得的性质我们知道这个连续块的 LCA 一定会被更新到),即 dp[i][j]=min(dp[i][j],dp[i2][j1]+dep[LCA(id[i1],id[i])])

    接下来可以考虑 dp 的优化了。时间上,我们发现一对相邻的点的 LCA 在转移时可能被用到多次,我们可以先预处理出所有相邻点的 LCA ,再直接拿过来用。空间上,可以考虑滚动,但还有个方法更优,我们考虑到 nk3105 ,那么我们只要开个一维数组即可。

Code

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<iostream>
#define M 300005
using namespace std;
template <class T>
inline void Rd(T &res){
    char c;res=0;int k=1;
    while(c=getchar(),c<48&&c!='-');
    if(c=='-'){k=-1;c='0';}
    do{
        res=(res<<3)+(res<<1)+(c^48);
    }while(c=getchar(),c>=48);
    res*=k;
}
template <class T>
inline void Pt(T res){
    if(res<0){
        putchar('-');
        res=-res;
    }
    if(res>=10)Pt(res/10);
    putchar(res%10+48);
}
void check(int &a,int b){
    if(a==-1||a>b)a=b;
}
struct edge{
    int v,nxt;
}e[M<<1];
int n,k,A[M];
int head[M],edgecnt;
int dep[M],fa[M],top[M],L[M],R[M],sz[M],son[M],tim;
void add_edge(int u,int v){
    e[++edgecnt].v=v;e[edgecnt].nxt=head[u];head[u]=edgecnt;
}
void dfs(int x,int t){
    if(~t)dep[x]=dep[t]+1;
    fa[x]=t;
    L[x]=++tim;
    sz[x]=1;
    for(int i=head[x];~i;i=e[i].nxt){
        int v=e[i].v;
        if(v==t)continue;
        dfs(v,x);
        sz[x]+=sz[v];
        if(sz[v]>sz[son[x]])son[x]=v;
    }
    R[x]=tim;
}
void rdfs(int x,int t,int tp){
    top[x]=tp;
    for(int i=head[x];~i;i=e[i].nxt){
        int v=e[i].v;
        if(v==t)continue;
        if(v==son[x])rdfs(v,x,tp);
        else rdfs(v,x,v);
    }
}
int LCA(int u,int v){
    while(top[u]!=top[v]){
        if(dep[top[u]]>dep[top[v]])u=fa[top[u]];
        else v=fa[top[v]];
    }
    return dep[u]<dep[v]?u:v;
}
int dp[M+60000];
int lca[M];
void solve(){
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    for(int i=1;i<n;i++)
    lca[i]=LCA(A[i],A[i+1]);
    for(register int i=1;i<=n;++i){
        int pre2=(i-2)*(k+1);
        int pre1=(i-1)*(k+1);
        int now=i*(k+1);
        for(register int j=0;j<=k;++j){
            check(dp[now+j],dp[pre1+j]);
            if(j>=1){
                if(~dp[pre1+j-1])check(dp[now+j],dp[pre1+j-1]+dep[A[i]]);
                if(i>=2&&~dp[pre2+j-1])check(dp[now+j],dp[pre2+j-1]+dep[lca[i-1]]);
            }
        }
    }
}
int main(){
    int a,b;
    while(scanf("%d",&n)!=EOF){
        memset(head,-1,sizeof(head));
        memset(son,0,sizeof(son));
        edgecnt=0;tim=0;
        dep[1]=1;
        Rd(k);
        for(int i=1;i<=n;++i)Rd(A[i]);
        for(int i=1;i<n;++i){
            Rd(a);Rd(b);
            add_edge(a,b);
            add_edge(b,a);
        }
        dfs(1,-1);
        rdfs(1,-1,1);
        solve();
        Pt(dp[n*(k+1)+k]);
        putchar('\n');
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值