Loading

P10219 [省选联考 2024] 虫洞 题解

P10219 [省选联考 2024] 虫洞 题解

前三个条件说明对于每种编号构成的子图都是形如若干个环,于是这启发我们对于每种颜色分别处理。考虑现在已经处理了编号为 \(1\sim i-1\) 的边,考虑加入编号为 \(i\) 的边,看什么条件下会合法。

不难发现合法的添加有两种:

  • 在一个连通块 \(G=(V,E)\) 中加边。加边方式形如一个循环移位(即一个大环)。并且可以发现当一个点的连边被确定后,整个连通块的加边方案就确定了。所以方案数为 \(|V|\)

  • 在从连通块 \(G_1=(V_1,E_1)\) 向连通块 \(G_2=(V_2,E_2)\) 连边。为了满足第四个条件,这两个连通块应该是同构的(同构是指可以对两个联通块中的点进行重标号使得这两个连通块相同,显然一个必要条件为 \(|V_1|=|V_2|\land |E_1|=|E_2|\))。同理,方案数为 \(|V_1|\)

由此我们可以发现两个性质:

  • 对于任意连通块,它是强连通的。

  • 一个连通块中的各个点是等价的。

  • 如果两个连通块一开始不同构,那么以后他们不可能在同构。

所以在一开始可以将同构的连通块放在一起单独处理,这可以通过 Hash 实现。发现转移的代价仅与连通块点数有关,这启示我们忽略其连通块具体构造而仅记录其点数进行处理。

\(f_{i,j}\) 表示将 \(i\) 个有 \(j\) 个点的连通块添加 \(K\) 种边的方案数。假设我们将同构的联通块放在一起形成若干组 \((G,|V|,cnt)\),则答案为 \(\prod f_{cnt,|V|}\)


现在考虑如何求 \(f\)

由于 \(f\) 的图中个连通块最后不一定合并成一个连通块,用一般图计数的思路,枚举第 \(n\) 个点在的连通块,化为处理连通图计数问题。

\(dp_{k,i,j}\) 表示添加了 \(k\) 种边,有 \(i\) 个含 \(j\) 个点的连通块,并最终合并成一个连通块的方案数,枚举第 \(i\) 个连通块所在的那个连通块,有转移:

\[f_{i,j}=\sum_{k=1}^idp_{K,k,j}f_{i-k,j}{i-1\choose k-1} \]


现在考虑如何求 \(dp_{}\)

枚举合并为几个连通块,有转移 \(dp_{k,i,j}=\sum_{x|i}dp_{k-1,x,\frac {ij}x}g_{i,x}res\)

其中 \(g_{i,j}\) 表示将 \(i\) 个点分为 \(j\) 个圆排列的方案数。考虑每次选出 \(\frac ij\) 个点进行圆排列的过程,方案数为 \(\frac{i!}{\frac ij(i-\frac ij)!}\times \frac{(i-\frac ij)!}{\frac ij(i-2\frac ij)}\times \cdots=\frac{i!}{(\frac ij)^j}\) 由于圆排列之间是无序的,再除以 \(j!\) 最终方案数为 \(\frac{i!}{j!(\frac ij)^j}\)

\(res\) 表示一次连边合并为 \(x\) 块,且两两同构的方案数。对于第 \(1\) 块里面的 \(\frac jx\) 可以随便连边,即 \(j^{\frac ix}\) 种,对于剩下的 \(x-1\) 块,前 \(\frac ix-1\) 可以随便连,最后一个必定可以使得整个图同构(画图可以理解),即 \(j^{(\frac ix-1)(x-1)}\) 种。两者乘起来为 \(j^{i-x+1}\) 种。

于是:

\[dp_{k,i,j}=\sum_{x|i}dp_{k-1,x,\frac {ij}x}\frac{i!}{x!(\frac ix)^x}j^{i-x+1} \]

\(h_{k,i,j}=\frac{dp_{k,i,j}}{j^{i}}\),整理可得 \(h_{k,i,j}=j\sum_{x|i}h_{k-1,x,j\times \frac {i}x}\frac{i!}{x!(\frac ix)^x}\),发现这次转移 \(h_{k,i,j}\) 相比 \(h_{k,i,1}\) 仅是多乘了一个 \(j\) ,而其第三维所有可以到的状态也是 \(\frac ix\)\(j\times \frac ix\) 即乘了一个 \(j\) 的区别,于是可以发现性质:\(h_{k,i,j}=j^{k-1}h_{k,i,1}\),进一步可得:

\[dp_{k,i,j}=j^{k+i-1}dp_{k,i,1} \]


\(q_{i,j}=dp_{i,j,1}\),则有:

\[q_{i,j}=\sum_{x|j}q_{i-1,x}\frac{j!}{x!(\frac jx)^x}(\frac jx)^{i+x-2}=\sum_{x|j}q_{i-1,x}\frac{(j-1)!}{(x-1)!}(\frac jx)^{i-1} \]

\(p_{i,j}=\frac{q_{i,j}}{(j-1)!j^{i-1}}\),则有:

\[p_{i,j}=\sum_{x|j}\frac{p_{i-1,x}}{x} \]

我们发现这是一个进行多次的迪利克雷卷积式,而我们知道一般可以由卷积 \(x\) 的结果卷上卷 \(y\) 次的结果得到卷 \(x+y\) 次的结果。

我们去探索这种表达式,首先推导一个 \(x\to x+2\) 的表达式,

\[\begin{aligned} &p_{i+2,j}\\ =&\sum_{x|j}\frac {p_{i+1,x}}{x}\\ =&\sum_{x|j}\sum_{y|x}\frac{p_{i,y}}{xy}\\ =&\sum_{y|x}\frac{p_{i,y}}{y^2}\sum_{x|\frac jy}\frac 1x\\ =&\sum_{y|j}\frac{p_{i,y}p_{2,\frac iy}}{y^2} \end{aligned} \]

于是我们猜测 \(p_{x+y,j}=\sum_{k|j}\frac{p_{x,k}p_{y,\frac jk}}{k^y}\),可以用数学归纳法证明确实是这样的。

这样通过倍增处理 \(p_{2^k,*}\)\(\mathcal O(n\log n\log k)\) 算出 \(p_{K,*}\)

于是上我们解决了这道题。


关于如何 Hash 判同构。

我们知道各个点是等价的,于是从任何一个点开始,优先通过编号更小的边开始DFS,将每条边起点 \(u\) 与 终点 \(v\) 的 dfn 值作为一组进行 hash 即可。

代码(注意 dp 各维顺序有变):

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int NN=2e3+5,MOD=998244353,N=2025;
typedef pair<int,int> pa;
int n,m,K;
int to[NN][NN],p[50][NN],p_final[NN],dp[NN][NN],fac[NN],f[NN],c[NN][NN],dfn[NN],num,h,ans=1,inv[100][NN];
vector<int> fc[NN];
int QP(int a,int b,int p=MOD){
    int c=1;
    for(;b;b>>=1){
        if(b&1)c=1ll*a*c%p;
        a=1ll*a*a%p;
    }
    return c;
}
void PreWork(){
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j+=i)fc[j].push_back(i);
    fac[0]=1;for(int i=1;i<=n;i++)fac[i]=1ll*fac[i-1]*i%MOD;
    c[0][0]=1;
    for(int i=1;i<=n;i++){
        c[i][0]=1;
        for(int j=1;j<=n;j++)c[i][j]=(c[i-1][j]+c[i-1][j-1])%MOD;
    }
    for(int i=0;i<=50;i++){
        int tmp=QP(2,i,MOD-1)*(MOD-2);
        for(int k=1;k<=n;k++)inv[i][k]=QP(k,tmp);
    }
    return;
}
void P(){
    for(int i=1;i<=n;i++)p[0][i]=1;
    p_final[1]=1;
    for(int i=1;i<=__lg(K);i++)
        for(int j=1;j<=n;j++)
            for(int k:fc[j])(p[i][j]+=p[i-1][k]*p[i-1][j/k]%MOD*inv[i-1][k])%=MOD;//QP(2,i-1)是放在指数上的,应该%(MOD-1)而非%MOD 
    for(int i=__lg(K);i>=0;i--){
        if((K>>i)&1){
            for(int j=n;j>=1;j--){
                int tmp=0;
                for(int k:fc[j])(tmp+=p_final[k]*p[i][j/k]%MOD*inv[i][k])%=MOD;
                p_final[j]=tmp;
            }
        } 
    }
    return;
}
void DP(){
    for(int i=1;i<=n;i++)
        dp[1][i]=p_final[i]*fac[i-1]%MOD*QP(i,K-1)%MOD;
    for(int j=1;j<=n;j++){
        int tmp=QP(j,K);
        for(int i=1;i<=n;i++){
            dp[j][i]=dp[1][i]*tmp%MOD;
            (tmp*=j)%=MOD;
        }
    }
    return;
}
int F(int i,int o){
    f[0]=1;
    for(int j=1;j<=o;j++){
        f[j]=0;
        for(int k=1;k<=j;k++)
            (f[j]+=c[j-1][k-1]*f[j-k]%MOD*dp[i][k])%=MOD;
    }
    return f[o];
}
void DFS(int x){
    if(dfn[x])return;
    dfn[x]=++num;
    for(int i=1;i<=m;i++){
        DFS(to[x][i]);
        h=(h*N+dfn[x]*dfn[x]%MOD*dfn[to[x][i]])%MOD;
    }
    return;
}
unordered_map<int,int> mp[NN];
signed main(){
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    cin>>n>>n>>m>>K;
    for(int i=1;i<=n*m;i++){
        int u,v,w;cin>>u>>v>>w;
        to[u][w]=v;
    }
    for(int i=1;i<=n;i++){
        if(dfn[i])continue;
        num=h=0;
        DFS(i);
        mp[num][h]++;
    }
    PreWork();
    P();
    DP();
    for(int i=1;i<=n;i++)
        for(auto o:mp[i])
            (ans*=F(i,o.second))%=MOD;
    cout<<ans;
    return 0;
}
posted @ 2025-02-10 22:14  lupengheyyds  阅读(22)  评论(0)    收藏  举报