树与图
- 如果真的在图上跑算法,那么光建图复杂度就O(n2logn)O(n^2logn)O(n2logn)了,这显然不可行。所以一定要把 在图上的操作 转换成 在树上的操作
- 在图上删去点u,相当于在树上删去u到根节点的链,并把u的整棵子树删掉
- 如果我们枚举算法可能产生的所有过程,然后再去求每个过程对应的删点次数,那么光枚举过程就已经可以T爆了
- 所以我们只能枚举删点次数,然后求出该删掉次数对应多少种过程
- 这可以用树形DP实现,时间复杂度O(n3)O(n^3)O(n3)
- 可以想到用生成函数进一步优化,即用次数来代表删点次数,系数来代表可能过程种数,这样合并两棵子树时直接用NTT把两棵子树的多项式乘起来即可
- 这样会产生两个问题:
-
代码中每一步合并乘的Cj+kkC_{j+k}^{k}Cj+kk怎么办?
-
NTT的时间复杂度是O(nlogn)O(nlogn)O(nlogn)(这里n指多项式的最高次数)\color{Red}(这里n指多项式的最高次数)(这里n指多项式的最高次数),这样总时间复杂度就是O(n3logn)O(n^3logn)O(n3logn),怎么还变大了?
就是这两个问题让我在考场上没有继续写下去
- 对于第一个问题,我后来想到,其实不一定要在DP过程中乘Cj+kkC_{j+k}^{k}Cj+kk,DP完后再统一乘(删点次数)!(删点次数)!(删点次数)!来定序也是可行的
- 对于第二个问题,在更新u时,直接用 分治NTT 把u的所有儿子上的多项式 一起 乘起来,不要那么傻逼想着用NTT一个接一个乘
这样时间复杂度降为O(n2log2n)O(n^2log^2n)O(n2log2n),但还是会T - 题目的时间复杂度很大程度上取决于NTT时多项式的最高次数,而多项式次数与删点次数有关,所以考虑一下我们最多能删几个点
- 发现所选的要删的点满足如下规律:所选的任意两个点都不互为祖先关系,且从根到每个叶子的路径上都恰好有一个被选择的点
- 即u点的多项式的最高次数<=u子树内叶节点的个数\color{Red}u点的多项式的最高次数<=u子树内叶节点的个数u点的多项式的最高次数<=u子树内叶节点的个数,进一步地,因为每个节点的多项式最高次数至少为1,所以u点的多项式最多是(u子树内叶节点个数)个多项式相乘的结果\color{Red}u点的多项式最多是(u子树内叶节点个数)个多项式相乘的结果u点的多项式最多是(u子树内叶节点个数)个多项式相乘的结果
- 设leafuleaf_uleafu表示u的子树中叶子的个数,则在u点分治NTT的时间总和<=leafu(log2leafu)<=leaf_u(log^2leaf_u)<=leafu(log2leafu),整道题的总时间<=∑u=1nleafu(log2leafu)<=\sum_{u=1}^{n}leaf_u(log^2leaf_u)<=∑u=1nleafu(log2leafu)
- 考虑两种极端情况,一种是菊花图,一种是一条很长的树链,上面随机的挂上一些类似菊花图(深度小但点度数大)的子树
- 按照上面的做法,菊花图的时间复杂度可以做到O(nlog2n)O(nlog^2n)O(nlog2n),树链的时间复杂度却逼近O(n2log2n)O(n^2log^2n)O(n2log2n)
- 我们考虑给树链换一种做法:
枚举在链上删了哪个点,这样一来整条链和被删点的子树都会被删除,而被删点上方的子树不会被删除,并且相互独立了。
因此,如果链上的点的多项式为F1(x),F2(x),F3(x)...F_1(x),F_2(x),F_3(x)...F1(x),F2(x),F3(x)...(按点深度由浅到深编号),那么最后求出这条链的多项式为:F1(x)+F1(x)F2(x)+F1(x)F2(x)F3(x)...F_1(x)+F_1(x)F_2(x)+F_1(x)F_2(x)F_3(x)...F1(x)+F1(x)F2(x)+F1(x)F2(x)F3(x)...
用分治NTT做,总时间<=leaf1(log2leaf1)<=leaf_1(log^2leaf_1)<=leaf1(log2leaf1),所以时间复杂度为O(nlog2n)O(nlog^2n)O(nlog2n) - 考虑把这两种做法结合起来,即对树进行重链剖分,然后对每个节点的轻儿子分治NTT,对每条重链分治 NTT
- 具体来说,设fu(x)f_u(x)fu(x)表示u点的多项式
深搜遍历每个节点,假设当前遍历到u
若u为叶子节点:fu(x)=xf_u(x)=xfu(x)=x,返回
若不是:执行下述步骤
①若节点u有轻儿子:fu(x)=∏v∈lightsonfv(x)f_u(x)=\prod_{v\in lightson}f_v(x)fu(x)=∏v∈lightsonfv(x)
若没有:fu(x)=1f_u(x)=1fu(x)=1
②若节点为重链端点:
(设这条重链上的点由浅至深分别为p1,p2,⋯,pkp_1,p_2,⋯,p_kp1,p2,⋯,pk。显然,p1p_1p1为链头,pkp_kpk为叶子节点。)
fp1(x)=fp1(x)+fp1(x)fp2(x)+fp1(x)fp2(x)fp3(x)+...+fp1(x)fp2(x)fp3(x)...fpk−1+xf_{p_1}(x)=f_{p_1}(x)+f_{p_1}(x)f_{p_2}(x)+f_{p_1}(x)f_{p_2}(x)f_{p_3}(x)+...+f_{p_1}(x)f_{p_2}(x)f_{p_3}(x)...f_{p_{k-1}}+xfp1(x)=fp1(x)+fp1(x)fp2(x)+fp1(x)fp2(x)fp3(x)+...+fp1(x)fp2(x)fp3(x)...fpk−1+x
(+x+x+x是因为要考虑删去自己的情况) - 分析一下这样做的时间复杂度,整道题的总时间<=∑u∈topleafu(log2leafu)+∑u=1n(∑v∈lightsonuleafv)[log2(∑v∈lightsonuleafv)]<=\sum_{u\in top}leaf_u(log^2leaf_u)+\sum_{u=1}^{n}(\sum_{v\in lightson_u}leaf_v)[log^2(\sum_{v\in lightson_u}leaf_v)]<=∑u∈topleafu(log2leafu)+∑u=1n(∑v∈lightsonuleafv)[log2(∑v∈lightsonuleafv)]
- ∑u∈topleafu(log2leafu)<=∑u∈topleafu(log2n)=log2n∑u∈topleafu\sum_{u\in top}leaf_u(log^2leaf_u)<=\sum_{u\in top}leaf_u(log^2n)=log^2n\sum_{u\in top}leaf_u∑u∈topleafu(log2leafu)<=∑u∈topleafu(log2n)=log2n∑u∈topleafu 。因为每个叶子节点到根节点的路径上最多有lognlognlogn条重链,而每个叶子节点又只在其到根节点的路径上的重链头处对∑u∈topleafu\sum_{u\in top}leaf_u∑u∈topleafu有1的贡献,所以∑u∈topleafu<=nlogn\sum_{u\in top}leaf_u<=nlogn∑u∈topleafu<=nlogn,∑u∈topleafu(log2leafu)<=nlog3n\sum_{u\in top}leaf_u(log^2leaf_u)<=nlog^3n∑u∈topleafu(log2leafu)<=nlog3n
- ∑u=1n(∑v∈lightsonuleafv)[log2(∑v∈lightsonuleafv)]<=∑u=1n(∑v∈lightsonuleafv)(log2n)=log2n∑u=1n(∑v∈lightsonuleafv)\sum_{u=1}^{n}(\sum_{v\in lightson_u}leaf_v)[log^2(\sum_{v\in lightson_u}leaf_v)]<=\sum_{u=1}^{n}(\sum_{v\in lightson_u}leaf_v)(log^2n)=log^2n\sum_{u=1}^{n}(\sum_{v\in lightson_u}leaf_v)∑u=1n(∑v∈lightsonuleafv)[log2(∑v∈lightsonuleafv)]<=∑u=1n(∑v∈lightsonuleafv)(log2n)=log2n∑u=1n(∑v∈lightsonuleafv) 。因为每个叶子节点到根节点的路径上最多有lognlognlogn条轻边,即每个叶子节点最多只在lognlognlogn个祖先处对∑u=1n(∑v∈lightsonuleafv)\sum_{u=1}^{n}(\sum_{v\in lightson_u}leaf_v)∑u=1n(∑v∈lightsonuleafv)有1的贡献,所以∑u=1n(∑v∈lightsonuleafv)<=nlogn\sum_{u=1}^{n}(\sum_{v\in lightson_u}leaf_v)<=nlogn∑u=1n(∑v∈lightsonuleafv)<=nlogn,∑u=1n(∑v∈lightsonuleafv)[log2(∑v∈lightsonuleafv)]<=nlog3n\sum_{u=1}^{n}(\sum_{v\in lightson_u}leaf_v)[log^2(\sum_{v\in lightson_u}leaf_v)]<=nlog^3n∑u=1n(∑v∈lightsonuleafv)[log2(∑v∈lightsonuleafv)]<=nlog3n
- 因此总时间复杂度是O(nlog3n)O(nlog^3n)O(nlog3n),但由于树剖logloglog小和跑不满等原因可以跑过
(最后树剖分轻重儿子维护的处理类似这题,可以一起记忆)
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
typedef long long ll;
const int mod=998244353;
const int N=100010;
const int M=250000;
struct Edge{int v,nxt;
}edge[N];
int n,cnt,head[N];
int fa[N],sz[N],son[N],ind,dfn[N],rnk[N],top[N],bot[N];
int rev[M],inv[M],w[20][M],iw[20][M],A[M],B[M];
vector<int> poly[N],f[N<<2],g[N<<2];
int add(int a,int b){a=a+b;if(a>mod) return a-mod;return a;
}
int dec(int a,int b){a=a-b;if(a<0) return a+mod;return a;
}
int mul(int a,int b){return 1ll*a*b%mod;
}
int power(int a,int b){int res=1;while(b){ if(b&1){res=mul(res,a);}b>>=1;a=mul(a,a);}return res;
}
void init(int len){inv[1]=1;for(int i=2;i<=len;i++) inv[i]=mul(inv[mod%i],dec(mod,mod/i));for(int i=1,t=0;i<len;i<<=1,t++){int wn=power(3,(mod-1)/i/2),iwn=power(wn,mod-2);for(int j=0;j<len;j+=(i<<1)){int w=1,iw=1;for(int k=j;k<j+i;k++,w=mul(w,wn),iw=mul(iw,iwn)){::w[t][k]=w;::iw[t][k]=iw;}}}
}
void NTT(int a[],int len,int op){//op=0:系数转点值,op=1:点值转系数 for(int i=0;i<len;i++){rev[i]=(rev[i>>1]>>1)|((i&1)*(len>>1));if(i<rev[i])swap(a[i],a[rev[i]]);}for(int i=1,t=0;i<len;i<<=1,t++){for(int j=0;j<len;j+=(i<<1)){int x,y;for(int k=j;k<j+i;k++){x=a[k];if(!op) y=mul(w[t][k],a[k+i]);else if(op) y=mul(iw[t][k],a[k+i]);a[k]=add(x,y);a[k+i]=dec(x,y);}}}if(op){for(int i=0;i<len;i++)a[i]=mul(a[i],inv[len]);}
}
vector<int> operator + (const vector<int> &a,const vector<int> &b){int n=a.size(),m=b.size();vector<int> c(max(n,m));for(int i=0;i<n;i++) c[i]=a[i];for(int i=0;i<m;i++) c[i]=add(c[i],b[i]);return c;
}
vector<int> operator * (const vector<int> &a,const vector<int> &b){int n=a.size(),m=b.size();vector<int> c(n+m-1);int len;for(len=1;len<=n+m;len<<=1);for(int i=0;i<len;i++){A[i]=i<n?a[i]:0;B[i]=i<m?b[i]:0;}NTT(A,len,0);NTT(B,len,0);for(int i=0;i<len;i++)A[i]=mul(A[i],B[i]);NTT(A,len,1);for(int i=0;i<n+m-1;i++) c[i]=A[i];return c;
}
void addedge(int u,int v){edge[++cnt].v=v;edge[cnt].nxt=head[u];head[u]=cnt;
}
void dfs1(int u){sz[u]++;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].v;dfs1(v);sz[u]+=sz[v];if(sz[v]>sz[son[u]]) son[u]=v;}
}
void dfs2(int u,int tp){dfn[u]=++ind;rnk[ind]=u;top[u]=tp;bot[tp]=u;if(!son[u]) return;dfs2(son[u],tp);for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].v;if(v!=son[u]) dfs2(v,v);}
}
void solve(vector<int> &sons,int u,int l,int r){if(l==r){f[u]=poly[sons[l]];return;}int mid=(l+r)>>1;solve(sons,u<<1,l,mid);solve(sons,u<<1|1,mid+1,r);f[u]=f[u<<1]*f[u<<1|1];f[u<<1].clear();f[u<<1|1].clear();
}
void merge(int u,int l,int r){if(l==r){f[u]=g[u]=poly[rnk[l]];return;}int mid=(l+r)>>1;merge(u<<1,l,mid);merge(u<<1|1,mid+1,r);f[u]=f[u<<1]*f[u<<1|1];g[u]=g[u<<1]+f[u<<1]*g[u<<1|1];//g=f1+f1f2+f1f2f3+...f[u<<1].clear();f[u<<1|1].clear();g[u<<1].clear();g[u<<1|1].clear();
}
void dp(int u){if(!son[u]){poly[u].push_back(0);//0*x^0poly[u].push_back(1);//1*x^1return;}dp(son[u]);vector<int> lis;for(int i=head[u];i;i=edge[i].nxt){int v=edge[i].v;if(v!=son[u]){dp(v);lis.push_back(v);}}if(lis.size()){solve(lis,1,0,lis.size()-1);//分治NTT合并轻儿子信息 poly[u]=f[1];}else{ poly[u].push_back(1);//1*x^0}if(u==top[u]){merge(1,dfn[u],dfn[bot[u]]-1);//分治NTT合并重链信息 //注意-1poly[u].clear();poly[u].push_back(0);//0*x^0for(int i=0,sz=g[1].size();i<sz;i++){poly[u].push_back(g[1][i]);}poly[u][1]=add(poly[u][1],1);//x^1项的次数+1 }
}
int main(){scanf("%d",&n);for(int i=2;i<=n;i++){scanf("%d",&fa[i]);addedge(fa[i],i);}dfs1(1);dfs2(1,1);int len=1;while(len<=n) len<<=1;init(len);dp(1);while((int)poly[1].size()<=n) poly[1].push_back(0);int ans=0,fac=1,x;for(int i=1;i<=n;i++){scanf("%lld",&x);fac=mul(fac,i);ans=add(ans,mul(x,mul(poly[1][i],fac)));}printf("%lld\n",ans);return 0;
}