P6773 NOI2020 命运
P6773 NOI2020 命运
数据结构上做 dp,少见但好用。
思路
首先我们用 dp 来解决这个问题。由于与祖先有关,我们不妨把一个节点的子问题限定在子树内,考虑所有从子树内连向子树外的集合 \(Q'\)。
设 \(f[u][i]\) 为 \(u\) 的集合 \(Q'\) 未被覆盖的祖先深度最大为 \(i\)(不存在未被覆盖时 \(i=0\)),一个儿子 \(v\) 向 \(u\) 合并时,有转移:
\[f[u][i]=\sum_{j=0}^{dep[u]} f[u][i]f[v][j]+\sum_{j=0}^i f[u][i]f[v][j]+\sum_{j=0}^{i-1} f[u][j]f[v][i]
\]
第一项为选择原树边 \((u,v)\),第二项为由 \(u\) 提供最大深度,第三项为由 \(v\) 提供最大深度(减 \(1\) 为了去重)。
考虑设计状态 \(g[u][i]=\sum_{j=0}^i f[u][j]\) 来优化转移。
原式可以写为:
\[f[u][i]=f[u][i]\times (g[v][dep[u]]+g[v][i])+g[u][i-1]\times f[v][i]
\]
显然初始值 \(f[u][\max_{(u,v)\in Q'}(dep[v])]=1\),其余为 \(0\)。
我们使用线段树来维护区间 \(f\) 的和,每次转移就是做一次线段树合并。
我们参考类似 cdq 分治维护 dp 的方法,从左到右转移时同时记录下当前的 \(g_u\) 和 \(g_v\),对于 \([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));
}

浙公网安备 33010602011771号