P5417 [CTSC2016] 萨菲克斯·阿瑞

P5417 [CTSC2016] 萨菲克斯·阿瑞

题意

\(m\) 种字符,每种字符有 \(c_i\) 个,你要选择一些字符组成长度为 \(n\) 的字符串。问所有合法字符串共有多少种不同的后缀数组。

\(n,m \le 500\)

思路

update 2025.08.18:优化了部分拗口的表述。

吐槽:一定是由于我的理解能力有很大问题,所以我真的觉得容斥的部分很难理解。想了好久才明白。这里给出应该是另一种容斥的理解方式。想看容斥可以直接跳到省流及其所在的部分。

update 2025.08.18:确实是我的理解能力有很大问题,现在看起来容斥的部分不难理解啊。

对于一个后缀数组 \(\{p_i \}\),它一定是一个排列。

考虑对于一个确定的 \(\{p_i\}\) 判定其合法性。那么原字符串 \(S\) 需要满足后缀的排序刚好是 \(p\)(废话)。

\(S_i\) 表示 \(S\) 的第 \(i\) 个字符。

  • \(S_{p_i} < S_{p_{i+1}}\),则很好,已经满足后缀的大小关系了。
  • \(S_{p_i} = S_{p_{i+1}}\),那么必须要有 \(rk_{p_i+1} < rk_{p_{i+1}+1}\)
  • 不可能有 \(S_{p_i} > S_{p_{i+1}}\)

其中 \(rk_i\) 表示后缀 \(i\) 的排名。

那么对于一个 \(p\),你求出对应的逆排列 \(rk\),然后枚举 \(p_1 \sim p_n\)

如果 \(rk_{p_i+1} < rk_{p_{i+1}+1}\),那么 \(S_{p_i} \le S_{p_{i+1}}\) 就可以了,否则必须要 \(S_{p_i} < S_{p_{i+1}}\)。于是我们得到了一个长度为 \(n-1\) 的不等号序列。

显然 \(S_{p_1} \sim S_{p_n}\) 是一个递增的序列。

那么我们贪心地填字符:从左往右扫不等号序列,从最小的字符开始填,对于所有 \(\le\),如果还有相等的数字就填相等的,否则填大 \(1\) 的数字。对于 \(<\) 就只能填大 \(1\) 的数字了。


你显然不能枚举 \(p\) 啊!

而且判定 \(p\) 是否合法的过程太复杂了,不好直接对 \(p\) 计数。

显然每个 \(p\) 可以得到唯一的不等号序列。但是不同的 \(p\) 可能得到一样的不等号序列!

不等号序列是由相邻 \(rk_{p_i+1}\) 之间的大小关系决定的。不妨认为 \(rk_{n+1}=0\)

我们把序列 \(\{p_i+1\}\) 记做 \(p'\)\(p,p',\{rk_{p_i'}\}\) 互为双射,即使它们的值域略有不同。

显然不同的 \(\{rk_{p_i'}\}\) 可能有相同的相邻大小关系

由于它们的双射关系,我们只需要计数 \(\{rk_{p_i'}\}\)(以下简称 \(rk'\))。这个也不好计。

但是合法的不等号序列是好 DP 计数的。所以我们计算每个合法的不等号序列对应多少个不同的 \(rk'\)


省流:定义 \(rk_i'=rk_{p_i+1}\),相邻大小关系指 \(rk_i'\)\(rk_{i+1}'\) 的大小关系。

对于一个合法的不等号序列,求其对应多少个不同的 \(rk'\)

每种不等号序列和每种 \(rk'\) 的相邻大小关系互为双射。每个 \(rk'\) 对应唯一的相邻大小关系。

但是每种相邻大小关系怎么计算有多少 \(rk'\) 呢?考虑容斥。

一个大小关系形如一个长度为 \(n\)\(<,>\) 组成的序列。我们按照 \(>\) 号把 \(rk'\) 分割成 \(k\) 段,每段长 \(l_1 \sim l_k\),那么每段内的 \(rk_i'\) 一定是单调递增的。

只满足每段内的 \(<\) 关系,方案数有 \(\binom{n}{l_1,l_2,\cdots ,l_k}\)

这个时候 \(>\) 关系不一定满足,方案数实际含义是,至多满足 \(k-1\) 个大于号关系的方案数,叫做 \(f_{k-1}\)

我们要求恰好满足 \(k-1\) 个大于号关系的方案数,即 \(f_{k-1} - f_{k-2}\)

\(f_{k-2}\),就钦定某一个 \(>\) 一定是 \(<\),然后计方案数,就是 \(\binom{n}{l_1,l_2,\cdots,l_{k-1}}\)。对于 \(k-1\) 个大于号都这么计,然后你又多减去了 \(1\) 次至少满足 \(k-3\) 个大于号关系的方案数。

所以总的容斥就是,对于一个确定的相邻大小关系序列,这个序列有 \(cnt\)\(>\) 号。把 \(k\)\(>\) 号钦定成 \(<\) 时,容斥系数为 \((-1)^k\)


然后我们边 DP 边容斥地计数。

DP 这个 \(rk'\) 的相邻大小关系序列长什么样子,计数有多少个满足这些相邻大小关系序列的 \(rk'\)

抄一遍:

不等号序列的 \(\le,<\)\(S_{p_i} \le S_{p_{i+1}}\)\(S_{p_i} < S_{p_{i+1}}\)
大小关系序列的 \(<,>\)\(rk_{p_i+1}\)\(rk_{p_{i+1}+1}\) 的大小关系。

  • 不等号序列的 \(\le\) 对应大小关系序列的 \(<\)
  • 不等号序列的 \(<\) 对应大小关系序列的 \(>\)

\(S_{p_i}\) 有关的不等号序列和与 \(rk_{p_i+1}\) 有关的大小关系序列互为双射。所以下文考虑对不等号序列求对应的 \(rk'\) 个数。

\(f_{i,j,k}\) 表示填字符串 \(S\),填完了前 \(i\) 种数字,字符串长度是 \(j\),目前最后一段不等号序列的连续 \(\le\)\(k\) 个。

你可能注意到了 \(S_{p_i}\)\(rk_{p_i+1}\) 的下标。\(rk'_i = rk_{p_i+1}\)\(p_i+1 \in [2,n+1]\)。可以认为 \(rk_{n+1}=0\),即 \(S_{n+1} = -inf\)

想想怎么转移可以把我们刚刚的那一坨容斥组合计数的东西都算进来。

  1. \(i\) 全填了,然后还是 \(\le\) 号:\(f_{i,j,k} \gets f_{i-1,j-c_i,k-c_i}\)
  2. \(i\)\(x\in[1,c_i]\) 个,然后是 \(<\) 号:\(f_{i,j,0} \gets f_{i-1,j-x,k} \times \frac{1}{(k+x)!}\)

有这些式子,组合数的部分就算完了,不过答案要乘上一个 \((n+1)!\)。不过我们还要把算多的容斥掉:

  1. 容斥的:\(f_{i,j,k} \gets f_{i-1,j-x,k-x} \times (-1)\)

感觉这是对的,但是不容易理解这是对的,但是它就是对的。这些式子和题解区其他式子是一样的。

\(1\) 可以合并到 \(3\),变成 \(x\in[1,c_i)\)

\(2,3\) 的话,枚举 \(i,j,k\),然后前缀和优化一下。具体是定义 \(g_{i,j,k} = \sum_{x=1} f_{i,j-x,k-x} = g_{i,j-1,k-1} + f_{i,j-1,k-1}\)。更具体的看代码。

初始 \(f_{0,0,0}=1\),答案是 \(n! \times f_{m,n,0}\)。这个 \(n!\) 来自组合数那里提出来的。

时间复杂度 \(O(n^3)\),空间复杂度 \(O(n^2)\),第一维滚掉。(\(n,m\) 同阶)。差不多是这样,注意转移边界细节。

我知道我很啰嗦,谁让我这么菜呢?有错求指出。

code

我还是认为细节很难想。有没有大神教教怎么思考细节/kel

#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace hesitate {
    constexpr int N=507,mod=1e9+7,Max=501;
    int add(int a,int b) { return a+b>=mod ? a+b-mod : a+b; }
    void _add(int &a,int b) { a=add(a,b); }
    int mul(int a,int b) { return 1ll*a*b%mod; }
    void _mul(int &a,int b) { a=mul(a,b); }
    int ksm(int a,int b=mod-2) {
        int s=1;
        while(b) {
            if(b&1) _mul(s,a);
            _mul(a,a);
            b>>=1;
        }
        return s;
    }
    int n,m,_m,c[N];
    int fac[N],ifac[N];
    void init() {
        fac[0]=1;
        rep(i,1,Max) fac[i]=mul(i,fac[i-1]);
        ifac[Max]=ksm(fac[Max]);
        per(i,Max-1,0) ifac[i]=mul(ifac[i+1],i+1);
    }
    int f[N][N],g[N][N];
    int ans;
    void main() {
        sf("%d%d",&n,&_m);
        rep(i,1,_m) {
            sf("%d",&c[i]);
            if(c[i]) c[++m] = c[i];
        }
        init();
        f[0][0]=1;
        rep(i,1,m) {
            rep(j,0,n) rep(k,0,j) {
                g[j][k] = add((j==0 || k==0) ? 0 : g[j-1][k-1], f[j][k]);
                f[j][k] = 0;
            }
            rep(j,1,n) rep(k,1,j) {
                _add(f[j][0], mul(ifac[k], add(g[j-1][k-1], k-c[i]-1<0 ? 0 : mod-g[j-c[i]-1][k-c[i]-1])));
                f[j][k] = add(mod-g[j-1][k-1], k-c[i]<0 ? 0 : g[j-c[i]][k-c[i]]);
            }
            _add(ans,f[n][0]);
        }
        pf("%d\n",mul(ans,fac[n]));
    }
}
int main() {
    #ifdef LOCAL
    freopen("in.txt","r",stdin);
    freopen("my.out","w",stdout);
    #endif
    hesitate :: main();
}
posted @ 2025-01-07 12:17  wing_heart  阅读(22)  评论(0)    收藏  举报