题目
一个长为n(n<=30)的原始序列x,x[i]可以取值0或1
一个长为m(m<=1e9)的点对序列(s,t),
s序列第i项和t的第i项,均可以取值[1,n],
如果构造好s和t后,对任意都存在01序列x使得,
则称这个序列是合法的,问种(s,t)序列中,有多少合法序列,
答案对998244353取模
思路来源
官方题解
https://www.cnblogs.com/chasedeath/p/14567667.html
题解
考虑1-n共n个点的不含自环的有向图,条边,如果可以用(i,j)边,表示
而最终x是要被化成两堆的,一堆0一堆1,也就是一个二分图
要求二分图的某一个带标号的边集方案只能被计一次,
有向图不好统计,可以考虑看成无向图,也就是(1,2)和(2,1)看成一种边
最后填入m个位置后再决定翻不翻转,再乘上对应的种选法
所以,需要统计的是有标号二分图的方案数
有标号二分图计数
计表示i个点j条边的二分图方案数,允许重复
即枚举二分图左边选了k个点,右边选了i-k个点,k*(i-k)条边里选j条,
这样的话,一个有着t个连通块的二分图会被计数次,
因为对应连通块部分可以左右互换,从而在另一种合法答案中被统计到
于是考虑怎么去重
计表示i个点j条边连通的二分图,
然而连通块数定义在状态里不好用于转移,所以后续的计数考虑容斥
通过减掉不合法的方案,
即枚举最后一个点所在的连通块多大,
对应连通块和之前的二分图是在什么时候断裂的
当大小为k时,从i-1个点中选k-1个点,再对应选出一些边
有
有了联通的二分图之后,再考虑如何合并
计表示i个点j条边由若干个连通块组成、无重复的二分图
转移仍然枚举最后一个点所在的连通块多大,
从之前的f合法方案,通过背包转移到新的合法方案
当大小为k时,从i-1个点中选k-1个点,再对应选出一些边
有
求出f[i][j]后,n个点是固定的,
因为f[n-1][j]可以通过不用边的方式转移到f[n][j]
所以,只需要用到f[n][j]的状态,无需再遍历f[i<n][j]的值
当有j条边时,需要满足j条边填到m个位置,j条边都至少出现一次,不然计数就会有重复
小球放盒问题
方法一
这等价于m个有区别的小球放入j个有区别的盒子,每个盒子不能为空
而第二类斯特林数S(m,j)为m个有区别的小球放入j个无区别的盒子的方案数,
所以求出乘上j个盒子的顺序即可,代码中用dp2[i]表示
方法二
互换小球和盒子,
dp[i]表示恰有i种球被放到了m个盒子里,每个盒子只能放一个球
那么就需要用全量的情况,减掉不合法的情况
有
代码
// Problem: G - Many Good Tuple Problems
// Contest: AtCoder - HHKB Programming Contest 2023(AtCoder Beginner Contest 327)
// URL: https://atcoder.jp/contests/abc327/tasks/abc327_g
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair<ll,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define scll(a) scanf("%lld",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
const int N=31,M=N*N,mod=998244353,inv2=(mod+1)/2;
int t,n,m,f[N][M],g[N][M],h[N][M],c[M][M],dp[M],dp2[M];
void ADD(int &x,int y){
x=(x+y)%mod;
}
int modpow(int x,int n,int mod){
int res=1;
for(;n;n>>=1,x=1ll*x*x%mod){
if(n&1)res=1ll*res*x%mod;
}
return res;
}
int C(int x,int y){
if(x<0 || y<0 || x<y)return 0;
return c[x][y];
}
void sol(){
sci(n),sci(m);
c[0][0]=1;
rep(i,1,M-1){
c[i][0]=c[i][i]=1;
rep(j,1,i-1){
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
}
rep(i,1,M-1){
dp[i]=modpow(i,m,mod);
rep(j,1,i-1){
ADD(dp[i],mod-1ll*c[i][j]*dp[j]%mod);
}
//printf("i:%d f:%d\n",i,dp[i]);
}
rep(i,1,M-1){
rep(j,1,i){
int sg=((i-j)&1)?-1:1;
ADD(dp2[i],(1ll*sg*C(i,j)%mod*modpow(j,m,mod)%mod)%mod);
}
//printf("i:%d f:%d\n",i,dp2[i]);
}
int ans=0;
rep(i,1,n){
int up=i*(i+1)/4;
rep(j,0,up){
rep(k,0,i){
ADD(g[i][j],1ll*C(i,k)*C(k*(i-k),j)%mod);
}
h[i][j]=g[i][j];
rep(k,1,i-1){
rep(l,0,j){
ADD(h[i][j],mod-1ll*C(i-1,k-1)*h[k][l]%mod*g[i-k][j-l]%mod);
}
}
f[i][j]=1ll*h[i][j]*inv2%mod;
rep(k,1,i-1){
rep(l,0,j){
ADD(f[i][j],1ll*C(i-1,k-1)*h[k][l]%mod*f[i-k][j-l]%mod*inv2%mod);
}
}
//printf("i:%d j:%d 1:%d 2:%d 3:%d\n",i,j,g[i][j],h[i][j],f[i][j]);
//printf("i:%d j:%d f:%d\n",i,j,f[i][j]);
//if(i==n && j<=m)printf("i:%d j:%d f:%d dp:%d add:%d\n",i,j,f[i][j],dp[j],1ll*f[i][j]*dp[j]%mod);
if(i==n)ADD(ans,1ll*f[i][j]*dp[j]%mod);
}
}
//pte(ans);
ans=1ll*ans*modpow(2,m,mod)%mod;
pte(ans);
}
int main(){
t=1;//sci(t); // t=1
while(t--){
sol();
}
return 0;
}