[WorldWide_D幻想乡♂模拟赛][JZOJ4599]西行妖

本文介绍了一种基于树形结构的动态规划算法,用于解决选择特定数量的叶子节点并将路径染色的问题。提供了两种不同的算法实现思路,一种是启发式合并优化的方法,另一种则是通过优化DFS序来降低时间复杂度。

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

Preface

今天射电磷(P5++5e)的模拟赛把大家都虐了~
最后一题个人认为质量很吼,就在这里记录一下。


题目大意

一棵1为根的树n个节点,你最多可以选择S叶子节点,然后将它们到根节点路径染黑。求有多少种方案能染黑至少m个节点。
1mn1000,1S20


题目分析

Algorithm 1

最暴力的dp就是设fi,j,k表示以i为根,选择了j个叶子,染黑了k个节点的方案数,转移子树合并乱搞一下即可。
然后Samjia直接使用启发式合并优化了这个算法,合并子树时使用类似合并果子的算法,用一个堆维护合并代价最小的两个块合并,时间复杂度应该是可以证明的。

Algorithm 2

以下是WorldWide_D标解。
fi,j,k表示统计到第i个叶子节点,已经选择了j个叶子,染黑了k个节点的方案数。
转移方程显然:

fi,j,k=fi,j1,k(high(i)high(lca(i,i)))

时间复杂度O(n3s)
比赛时我打了这个并且在实现时使用dfs,然而如果注意到dfs序的性质,可以将其优化。
DFN(i)<DFN(i),显然high(lca(i,i))随着DFN(i)的增大而不上升。
设当前节点为i,我们令
sj,k,d=lca(i,i)=dfi,j,k

再令

cntj,kd=sj,k,d

显然对于一个叶子节点fi,j,k=cntj1,khigh(i),然后更新sj,k,high(i)以及cnt
然后当退出一棵子树时,我们的i对原本子树中的点取lca深度肯定会加1,因此令原本子树根节点深度为d,我们要将sj,k,d的值加到sj,k,d1里面,然后清空sj,k,d,同时更新cnt数组。
具体细节比较多,可能会有些抽象,希望读者自行思考。
时间复杂度O(n2s)

代码实现

#include <iostream>
#include <cstdio>
#include <cctype>
#include <cmath>

using namespace std;

int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
    while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    return x*f;
}

const int P=1000000007;
const int N=1005;
const int E=N<<1;
const int S=22;

int fa[N],high[N],last[N],next[E],tov[E],d[N],p[N],pos[N];
int f[N][S][N],cnt[S][N],sum[S][N][N];
bool vis[N];
int n,m,s,tot,l,ans;

void insert(int x,int y){tov[++tot]=y,next[tot]=last[x],last[x]=tot;}

void dfs(int x)
{
    for (int i=last[x],y;i;i=next[i])
        high[y=tov[i]]=high[x]+1,d[x]++,dfs(y);
}

void dp(int x)
{
    if (!d[x])
    {
        p[++l]=x;
        f[l][1][high[x]]=1;
        for (int j=1;j<=s;j++)
            for (int k=1;k<=n;k++)
                if (k-high[x]>=0)
                    (f[l][j][k]+=cnt[j-1][k-high[x]])%=P;
        for (int j=1;j<=s;j++)
            for (int k=1;k<=n;k++)
            {
                if (!f[l][j][k]) continue;
                (sum[j][k][high[x]]+=f[l][j][k])%=P;
                if (k-high[x]>=0) (cnt[j][k-high[x]]+=f[l][j][k])%=P;
            }
    }
    else for (int i=last[x],y;i;i=next[i]) dp(y=tov[i]);
    if (x==1) return;
    for (int j=1;j<=s;j++)
        for (int k=1;k<=n;k++)
        {
            if (!sum[j][k][high[x]]) continue;
            if (k-high[x]>=0) (((cnt[j][k-high[x]]-=sum[j][k][high[x]])%=P)+=P)%=P;
            (sum[j][k][high[x]-1]+=sum[j][k][high[x]])%=P;
            if (k-high[x]+1>=0) (cnt[j][k-high[x]+1]+=sum[j][k][high[x]])%=P;
            sum[j][k][high[x]]=0;
        }
}

int main()
{
    freopen("tree.in","r",stdin),freopen("tree.out","w",stdout);
    n=read(),m=read(),s=read();
    for (int i=2;i<=n;i++) fa[i]=read(),insert(fa[i],i);
    high[1]=1,dfs(1);
    l=0,dp(1),ans=0;
    for (int i=1;i<=l;i++)
        for (int j=1;j<=s;j++)
            for (int k=m;k<=n;k++)
                (ans+=f[i][j][k])%=P;
    printf("%d\n",ans);
    fclose(stdin),fclose(stdout);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值