*题解:P9374 「DROI」Round 2 单图
解析
通过手玩发现所谓单图就是最长链长不超过 \(1\) 的图,由若干二元环和若干所有点要么入度为 \(0\),要么出度为 \(0\) 的弱连通图组成。
故设 \(f_{i,j,k}\) 表示由 \(i\) 个点组成的有 \(j\) 个二元环和 \(k\) 个弱连通图的有标号单图数量。但是这样的话让点 \(i\) 加入到弱连通图的情况就不好转移了,因为我们并不知道每个弱连通图中有多少个入度/出度为 \(0\) 的点。事实上,若 \(i\) 为出度为 \(0\) 的点,则我们只关心它被哪些入度为 \(0\) 的点连接;若 \(i\) 为入度为 \(0\) 的点,则我们只关心它连接哪些出度为 \(0\) 的点。
故更改状态,设 \(f_{i,j,k}\) 表示由 \(i\) 个点组成的有 \(j\) 个二元环和 \(k\) 个入度为 \(0\) 的点的有标号单图数量。那么当 \(i\) 为入度为 \(0\) 的点时,其可以连接所有出度为 \(0\) 的点,共有 \(i - 2j - k\) 个,至少连一个,方案数为 \(f_{i - 1,j,k - 1} \cdot (2^{i - 2j - k} - 1)\),当 \(i\) 为出度为 \(0\) 的点时,同理,方案数为 \(f_{i - 1,j,k}\cdot 2^k\)。当 \(i\) 用来组成二元环时,方案数为 \(f_{i - 2,j - 1,k} \cdot (i - 1)\)。
然而,这样做的话我们会遗漏掉存在标号较大的点向标号较小的点的连边的单图。这是因为我们在只考虑前 \(i\) 个点的情况下进行连边,这样就无法连接到后面的点。对此,我们可以预先钦定最后有多少个入度为 \(0\) 的点和出度为 \(0\) 的点。这样,只需考虑所有入度为 \(0\) 的点向哪些点连边。设有 \(x\) 个入度为 \(0\) 的点,\(y\) 个出度为 \(0\) 的点,考虑出度为 \(0\) 的点集的所有 \(2^y - 1\) 个非空子集,那么方案数即为 \((2^y - 1) ^ x \cdot \binom{x + y}{x}\)。组合数用于分配标号。
所以,对于 \(i\) 不用来组成二元环的情况,方案数为 \(f_{2j,j,0} \cdot (2^{i - 2j - k} - 1) ^ k \cdot \binom{i - 1}{2j} \cdot \binom{i - 2j}{k}\)。
综上,转移式为:
考虑优化,将单图中只有二元环的情况与单图中没有二元环的情况分开进行计数再进行合并。具体地,设 \(f_{i}\) 表示 \(i\) 个点组成的有标号单图数量;\(g_i\) 表示 \(i\) 个点组成的无二元环有标号单图数量;\(h_i\) 表示 \(i\) 个点组成的只有二元环的有标号单图数量。那么有
预处理即可。时间复杂度 \(O(n ^ 2\log n)\),\(\log\) 来自于快速幂,当然你也可以通过预处理来把它优化掉。
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000 + 5,M = 300000 + 5,K = 10000 + 5;
int qmi(int a,int b,int mod){
int res = 1;
while(b){
if(b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
int f[N],g[N],h[N],c[N][N];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
// freopen("in.txt","r",stdin);
// freopen("out1.txt","w",stdout);
int T,mod;
cin>>T>>mod;
for(int i=0;i<N;i++){
c[i][0] = 1;
}
for(int i=1;i<N;i++){
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<N;i++){
for(int j=0;j<=i;j++){//j 个入度为 0
g[i] = (g[i] + 1ll * qmi((qmi(2,i - j,mod) + mod - 1) % mod,j,mod) % mod * c[i][j] % mod) % mod;
}
}
h[0] = 1;
for(int i=2;i<N;i += 2){
h[i] = 1ll * h[i - 2] * (i - 1) % mod;
}
for(int i=1;i<N;i++){
for(int j=0;j <= i / 2;j++){//j 个二元环
f[i] = (f[i] + 1ll * h[2 * j] * g[i - 2 * j] % mod * c[i][2 * j] % mod) % mod;
}
}
while(T--){
int n;
cin>>n;
cout<<f[n]<<'\n';
}
return 0;
}
/*
*/

浙公网安备 33010602011771号