Loading

ARC121E 题解

(Link,700pts)

解决这类“所有元素都不满足某个条件”的计数问题,要么直接上手,要么反过来变成“至少有一个元素满足这个条件”来做容斥。在这题我们选择后者,思考的过程中它的意义会逐渐显现。

\(S(i)\) 表示 \((\)确定有 \(i\)\(k\) 满足 \(a_k\) 可以走到 \(k)\) 的排列数,那么套上容斥的公式有

\[ans=\sum_{i=0}^n (-1)^i S(i) \]

P.S. 像我一样的容斥萌新如果不知道这个公式是怎么来的,可以参见 这篇blog。我自己在思考的过程中又产生了亿些疑问,详见 这个讨论 (估计就我一个会问出来这种nt问题)


为了求 \(S\),我们开始DP。设 \(f_{u,i}\) 表示 \((u\) 的子树中有 \(i\)\(k\) 满足 \(a_k\) 也在 \(u\) 的子树内且 \(a_k\) 可以走到 \(k)\) 的方案数,分Ⅰ. \((u\)\(u\) 子树(不含 \(u\))中某个 \(k\)\(a_k)\)(即对于这个 \(k\)\(u=a_k\) 可以走到 \(k\)) 和Ⅱ. \((u\) 不是子树(不含 \(u\))中任何 \(k\)\(a_k)\) 进行讨论,设 \(siz_u\) 表示 \(u\) 的子树(含)的大小。

Ⅰ.我们先把 Ⅱ.的部分求出来,随后由 \(siz_u\)\(1\) 从大到小扫一遍,令

\[\forall i\in[1,siz_u],f_{u,i}\gets f_{u,i}+f_{u,i-1}\times((siz_u-1)-(i-1)) \]

即可,其中 \(siz_u-1\)\(u=a_k\) 时其不含自身的子树中待选的 \(k\) 的数目,\(i-1\)\(u\) 的儿子们已经选择的 \(k\) 的数目。

Ⅱ.难点(大概)来了。

我一开始想着对于每个儿子 \(v\),它的贡献是

\[\sum_{j=0}^{siz_v}(\text{所有除了}v\text{以外的}u\text{的儿子的子树中}a_k\text{可以走到}k\text{的数目和为}i-j的方案数总和) \]

后面这一串我自己都基本看不懂,想了一下发现要把它求出来起码需要 \(\Omega(n^4)\) 的复杂度,而最重要的是它会把答案算重。

转换思路,把 \(u\) 的儿子编号为 \(v_1,\cdots,v_s\),对于 \(v_p\),设 \(sf_{p-1,i}\) 为前 \(p-1\) 个儿子大力协同达到 \(i\) 个能够走到 \(k\)\(a_k\) 的方案数,则 \(sf_{p,i}=\sum_{j=0}^i f_{v,j}\times sf_{p-1,i-j}\),最后 \(f_{u,i}\) 即为 \(sf_{s,i}\)。具体实现如下:

int f[N][N],cf[N],siz[N];//cf即sf的滚动数组形态
 
void fdp(int u){
    f[u][0]=siz[u]=1;for(int v:vc[u]) fdp(v),siz[u]+=siz[v];
    for(int v:vc[u]){
        memcpy(cf,f[u],(siz[u]+1)<<2);
        memset(f[u],0,(siz[u]+1)<<2);
        for(int i=0;i<=siz[u];++i) for(int j=0;j<=siz[v];++j) (f[u][i+j]+=1ll*cf[i]*f[v][j]%mod)%=mod;
    }
    for(int i=siz[u];i;--i) (f[u][i]+=1ll*f[u][i-1]*(siz[u]-i)%mod)%=mod;
}

这种“滚雪球”的方式首先通过规定每个元素只能与前方所有元素发生关系(?)的方式杜绝了答案重复的情况发生,接着以前缀和把 \(\Theta(n^2)\) 的复杂度优化成了 \(\Theta(n)\),实在妙哉,在很多DP题目中都有应用,是解决“每个元素要和(剩下所有元素)一起构成贡献,求整体答案”一类问题的利器。

求完所有 \(f\) 后,剩下的 \(n-i\) 个位置可以随便排,所以有 \(S(i)=f_{1,i}\times (n-i)!\)


然而以上代码的复杂度咋看咋不对,分析一下果然(以下设 \(son(x)\)\(u\) 的儿子集合,\(sub(x)\)\(u\) 的子树,\(fa(x)\)\(x\) 的父亲):

\[\sum siz_u\sum_{v\in son(u)} siz_v=\sum (siz_u)^2=\Theta(n^3) \]

我们进行一个小的优化。发现在枚举到 \(v_p\) 时,\(sf_{p,i-1}\)\(i>\sum_{p'=1}^{p-1} siz(v_{p'})\) 的部分全部是 \(0\),根本作不了贡献,于是我们动态更新 siz[u],每次 \(i\)\(0\) 枚举到 \(\sum_{p'=1}^{p-1} siz(v_{p'})\) 即可:

int f[N][N],cf[N],siz[N];

void fdp(int u){
    f[u][0]=siz[u]=1;for(int v:vc[u]){fdp(v);
        memcpy(cf,f[u],(siz[u]+1)<<2);
        memset(f[u],0,(siz[u]+1)<<2);
        for(int i=0;i<=siz[u];++i) for(int j=0;j<=siz[v];++j) (f[u][i+j]+=1ll*cf[i]*f[v][j]%mod)%=mod;
        siz[u]+=siz[v];
    }
    for(int i=siz[u];i;--i) (f[u][i]+=1ll*f[u][i-1]*(siz[u]-i)%mod)%=mod;
}

此时我们的复杂度:

\[\sum_{u,son(u)=\{v_1,\cdots,v_s\}} \sum_{i=1}^s siz_{v_i}\sum_{j=1}^{i-1} siz_{v_j} \]

\[=\frac{\sum_{fa(x)=fa(y)} siz_x\times siz_y}{2} \]

可以发现对于一对 \((x,y):fa(x)=fa(y)\)\(siz_x\times siz_y\)

\[|\{(b,c):b\in sub(x),c\in sub(y)\}| \]

且这些 \((b,c)\) 只会对 \((x,y)\) 一个点对作出贡献(因为 \(\operatorname{lca(b,c)}\) 是固定的,在 \(b\) 到根的路径上的 \(x\) 和在 \(c\) 到根的路径上的 \(y\) 中满足 \(fa(x)=fa(y)\)\((x,y)\) 只有一对)。这棵树上的点对 \((b,c)\) 只有 \(\Theta(n^2)\) 个,所以总复杂度 \(\Theta(n^2)\)


回到开头,我们为何要用容斥?答案是显然的:题目中要求 \(a\)\(\{1,\cdots,n\}\) 的排列,而我们设计的DP状态把 \(a_k\) 限定到了一个个子树的范围内,而子树是不交的,所以不可能出现重复。设若我们不容斥直接就每个 \(a_k\) 都不能走到 \(k\) 的方案数进行DP,满足条件的 \(a_k\) 可能不知道飞哪去了,很难在转移时关注到所有 \(a_k\) 的分布状态,\(a\) 中没有重复的元素的限制没法达到;反之,不满足条件的 \(a_k\) 一定在 \(k\) 到根的路径上,便于我们直接对子树进行DP。这便是“正男♂则反”的妙处所在。


这题我在赛时想了1h毫无头绪,赛后下午对着题解盯了4h仍不知所云,晚上接着看了4h才似懂非懂地感觉自己了解了DP转移的思路,第二天下午浏览各种做过的DP题花3h方才知道这是个套路、做到了理解与方法迁移,晚上花最后的4h解决了诸如复杂度分析和容斥公式等零碎的问题才真正把这道题目理顺打通。要说融会贯通那我还做不到,但这么长时间的思考以及今天早上花4h写的这篇总结着实让我对DP的感觉有了较大的提升,对DP一窍不通的我可以说算是入门了。思考的过程是快乐的,收获进步更是快乐的,在DS上已经花很多时间的我以后我会把%你赛的重点放在DP和思维题上,遇到一些高(du)级(liu)的题目应该还会像这题一样多想几天、试着举一反三,把%你赛的作用发挥到最大。

说了这么多,我最深的切身体会便是:WTCL。

posted @ 2022-08-03 15:46  Albertvαn  阅读(24)  评论(0编辑  收藏  举报