[ARC086]E - Smuggling Marbles 树型DP

xiaoxiao2021-02-28  19

题面 首先可以发现以0为根时,层与层之间时独立的,于是就有了一种O(n^2)的做法:对每一层DP一次,设f[d][i][0/1]表示当前考虑第d层的贡献,i的子树中贡献0/1的方案数。那么最后我们得到很多f[][0][0/1],然后问题就变成从每个f[d][0][0/1]只能选其一,选中的数相乘并乘上选的f[][0][1]的个数再求总和,随便计数一下就好。 接着我们考虑怎么把这么所有层放在一起DP,我们重新定义一下状态f[d][i][0/1/2]表示考虑i下面的第d层,使得i上有0/1/多个的方案数。就是一个点i有一串dp状态。 然后就有一种类似启发式合并的做法,父亲先继承下状态最多的那个儿子,然后合并其他儿子的答案。 其实这样合并是O(n)的。因为同一层的两个点u,v只会在其LCA合并一次,然后同层所有点两两的LCA最多有该层点数-1个(很容易证)。所以总合并次数是O(n)级别的。 注意写的时候不能对状态最多的那个儿子做任何遍历之类的操作,否则复杂度GG。 代码:

#include<iostream> #include<cstdio> #include<algorithm> #include<vector> #define ll long long #define pll pair<ll,ll> using namespace std; const int maxn=200010; const int mod=1000000007; int n,id[maxn],d[maxn],num; ll mor[maxn]; vector<pll > f[maxn]; struct edge { int t; edge *next; }*con[maxn]; void ins(int x,int y) { edge *p=new edge; p->t=y; p->next=con[x]; con[x]=p; } void dp(int v,int fa) { int mx=n+1; for(edge *p=con[v];p;p=p->next) if(p->t!=fa) { dp(p->t,v); if(d[p->t]>d[mx]) mx=p->t; } if(mx<=n) id[v]=id[mx],d[v]=d[mx]+1; else id[v]=++num; f[id[v]].push_back(make_pair(1,1)); bool flag=0; for(edge *p=con[v];p;p=p->next) if(p->t!=fa&&p->t!=mx) { flag=1; for(int i=d[v]-d[p->t]-1;i<d[v];i++) { pll t=f[id[p->t]][i-d[v]+d[p->t]+1]; mor[i]=(mor[i]*(t.first+t.second)%mod+f[id[v]][i].second*t.second%mod)%mod; f[id[v]][i].second=(f[id[v]][i].second*t.first%mod+f[id[v]][i].first*t.second%mod)%mod; f[id[v]][i].first=f[id[v]][i].first*t.first%mod; } } if(flag) for(int i=0;i<d[v];i++) f[id[v]][i].first=(f[id[v]][i].first+mor[i])%mod,mor[i]=0; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { int x; scanf("%d",&x); ins(i,x); ins(x,i); } d[n+1]=-1; dp(0,-1); ll tot=1,sum=0; for(int i=0;i<=d[0];i++) { int sz=(f[id[0]][i].first+f[id[0]][i].second)%mod; sum=(sum*sz%mod+f[id[0]][i].second*tot%mod)%mod; tot=tot*sz%mod; } printf("%lld",sum); return 0; }
转载请注明原文地址: https://www.6miu.com/read-1650092.html

最新回复(0)