P6773 NOI2020 命运

P6773 NOI2020 命运

数据结构上做 dp,少见但好用。

思路

首先我们用 dp 来解决这个问题。由于与祖先有关,我们不妨把一个节点的子问题限定在子树内,考虑所有从子树内连向子树外的集合 Q

f[u][i]u 的集合 Q 未被覆盖的祖先深度最大为 i(不存在未被覆盖时 i=0),一个儿子 vu 合并时,有转移:

f[u][i]=j=0dep[u]f[u][i]f[v][j]+j=0if[u][i]f[v][j]+j=0i1f[u][j]f[v][i]

第一项为选择原树边 (u,v),第二项为由 u 提供最大深度,第三项为由 v 提供最大深度(减 1 为了去重)。

考虑设计状态 g[u][i]=j=0if[u][j] 来优化转移。

原式可以写为:

f[u][i]=f[u][i]×(g[v][dep[u]]+g[v][i])+g[u][i1]×f[v][i]

显然初始值 f[u][max(u,v)Q(dep[v])]=1,其余为 0

我们使用线段树来维护区间 f 的和,每次转移就是做一次线段树合并。

我们参考类似 cdq 分治维护 dp 的方法,从左到右转移时同时记录下当前的 gugv,对于 [i,i] 的线段树叶子按照上述转移方程转移;对于存在一个节点没有的情况,不难发现转移中只有一项产生贡献,相当于打上区间乘法标记。

CODE

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define mod 998244353

const int maxn=5e5+5;

struct Edge
{
    int tot;
    int head[maxn];
    struct edgenode{int to,nxt;}edge[maxn*2];
    inline void add(int x,int y)
    {
        tot++;
        edge[tot].to=y;
        edge[tot].nxt=head[x];
        head[x]=tot;
    }
}T;

int n,m;
int dep[maxn],rt[maxn];

vector<int>E[maxn];

ll sumu,sumv;

namespace linetree
{
    #define lch(p) tr[p].lch
    #define rch(p) tr[p].rch
    int tot;
    struct treenode{int lch,rch;ll sum,tag;}tr[maxn*20];
    inline int newnode(){tr[++tot].tag=1;return tot;}
    inline void pushup(int p){tr[p].sum=(tr[lch(p)].sum+tr[rch(p)].sum)%mod;}
    inline void pushdown(int p)
    {
        if(tr[p].tag!=1)
        {
            if(lch(p)) tr[lch(p)].sum=(tr[lch(p)].sum*tr[p].tag)%mod;
            if(lch(p)) tr[lch(p)].tag=(tr[lch(p)].tag*tr[p].tag)%mod;
            if(rch(p)) tr[rch(p)].sum=(tr[rch(p)].sum*tr[p].tag)%mod;
            if(rch(p)) tr[rch(p)].tag=(tr[rch(p)].tag*tr[p].tag)%mod;
            tr[p].tag=1;
        }
    }
    inline void change(int &p,int l,int r,int pos,ll val)
    {
        if(!p) p=newnode();
        if(l==r){tr[p].sum=val,tr[p].tag=1;return ;}
        pushdown(p);
        int mid=(l+r)>>1;
        if(pos<=mid) change(lch(p),l,mid,pos,val);
        else change(rch(p),mid+1,r,pos,val);
        pushup(p);
    }
    inline ll qry(int p,int l,int r,int lx,int rx)
    {
        if(r<lx||l>rx||!p) return 0;
        if(lx<=l&&r<=rx) return tr[p].sum;
        pushdown(p);
        int mid=(l+r)>>1;
        return (qry(lch(p),l,mid,lx,rx)+qry(rch(p),mid+1,r,lx,rx))%mod;
    }
    inline void merge(int &p,int tp,int l,int r)
    {
        if(!p&&!tp) return ;
        if(!p)
        {
            sumv=(sumv+tr[tp].sum)%mod;
            tr[tp].tag=(tr[tp].tag*sumu)%mod;
            tr[tp].sum=(tr[tp].sum*sumu)%mod;
            p=tp;
            return ;
        }
        else if(!tp)
        {
            sumu=(sumu+tr[p].sum)%mod;
            tr[p].tag=(tr[p].tag*sumv)%mod;
            tr[p].sum=(tr[p].sum*sumv)%mod;
            return ;
        }
        else if(l==r)
        {
            ll tmp=tr[p].sum;
            sumv=(sumv+tr[tp].sum)%mod;
            tr[p].sum=(tr[p].sum*sumv%mod+tr[tp].sum*sumu%mod)%mod;
            sumu=(sumu+tmp)%mod;
            return ;
        }
        pushdown(tp);pushdown(p);
        int mid=(l+r)>>1;
        merge(lch(p),lch(tp),l,mid);merge(rch(p),rch(tp),mid+1,r);
        pushup(p);
    }
}

inline void dfs(int u,int f)
{
    dep[u]=dep[f]+1;int mxd=0;
    for(auto v:E[u]) mxd=max(dep[v],mxd);
    linetree::change(rt[u],0,n,mxd,1);
    for(int i=T.head[u];i;i=T.edge[i].nxt)
    {
        int v=T.edge[i].to;
        if(v==f) continue;
        dfs(v,u);
        sumv=linetree::qry(rt[v],0,n,0,dep[u]);sumu=0;
        linetree::merge(rt[u],rt[v],0,n);
    }
}

int main()
{
    freopen("destiny.in","r",stdin);
    freopen("destiny.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        T.add(u,v),T.add(v,u);
    }
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        E[v].emplace_back(u);
    }
    dfs(1,0);
    printf("%lld",linetree::qry(rt[1],0,n,0,0));
}
posted @   彬彬冰激凌  阅读(4)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示