UOJ#214. 【UNR #1】合唱队形

链接:http://uoj.ac/problem/214

数据分治

PART I:
记f(t)表示t时刻还没有结束的概率,那么ans = ∑[t = 0 to inf]f(t)

考虑计算f(t),容斥,2 ^ (n - m)枚举哪些已经成为可行区间

f(t) = ∑[i = 1 to 2 ^ (n - m)] g(h(i), t) * (-1) ^ (bitcount(i) + 1)

h(i)表示满足i这个状态每个人必须学会的音符个数之和

g(i, t)相当于一共有tot个数,取t次,有i个关键点,不是所有关键点都被取过的概率,仍然考虑容斥

g(i, t) = ∑[j = 1 to i] C(i, j) * (-1) ^ (bitcount(j) + 1) * ((tot - j) / tot) ^ t

把∑[t = 0 to inf]x ^ t化简成1 / (1 - x)计算即可


PART II:

注意到一个点需要学会的音符个数只跟前m个数是否为1有关,而g的值只跟h(i)有关,所以我们统计h(i)出现次数

dp(i, j, k)表示当前考虑到第i个数,前m个数的状态为j,当前的h之和为k的方案数

转移很简单,就不说了

然后数据分治一下就好了

#include <bits/stdc++.h>
#define xx first
#define yy second
#define mp make_pair
#define pb push_back
#define fill( x, y ) memset( x, y, sizeof x )
#define copy( x, y ) memcpy( x, y, sizeof x )
using namespace std;

typedef long long LL;
typedef pair < int, int > pa;

inline int read()
{
	int sc = 0, f = 1; char ch = getchar();
	while( ch < '0' || ch > '9' ) { if( ch == '-' ) f = -1; ch = getchar(); }
	while( ch >= '0' && ch <= '9' ) sc = sc * 10 + ch - '0', ch = getchar();
	return sc * f;
}

const int MAXN = 35;
const int MAXM = 1005;
const int mod = 998244353;

inline void inc(int &x, int y) { x += y; while( x >= mod ) x -= mod; }
inline void dec(int &x, int y) { x -= y; while( x < 0 ) x += mod; }

int n, m, can[MAXN], tot, C[MAXM][MAXM], g[MAXM], a[MAXM], ans;
bool ok[MAXN];
char ch[MAXN];

inline int qpow(int x, int y) { int ret = 1; for( ; y ; y >>= 1, x = 1LL * x * x % mod ) if( y & 1 ) ret = 1LL * ret * x % mod; return ret; }

namespace part1
{
	int b[MAXN];

	inline void dfs(int st, int cur)
	{
		if( st == n - m + 2 )
		{
			if( !cur ) return ;
			int cnt = 0;
			for( int i = 1 ; i <= n ; i++ ) cnt += __builtin_popcount( b[ i ] );
			if( cur & 1 ) dec( ans, g[ cnt ] ); else inc( ans, g[ cnt ] );
			return ;
		}
		dfs( st + 1, cur );
		if( ok[ st ] )
		{
			int pre[MAXN];
			copy( pre, b );
			for( int i = 1 ; i <= m ; i++ ) b[ st + i - 1 ] |= 1 << ch[ i ] - 'a';
			dfs( st + 1, cur + 1 );
			copy( b, pre );
		}
	}
	
	inline void solve()
	{
		dfs( 1, 0 );
		printf( "%d\n", ans );
	}
}

namespace part2
{
	int f[MAXN][1030][355], h[2055];

	inline void solve()
	{
		for( int i = 1 ; i < ( 1 << m ) ; i++ )
		{
			h[ i ] = 0;
			for( int j = 1 ; j <= m ; j++ ) if( i >> j - 1 & 1 ) h[ i ] |= 1 << ch[ j ] - 'a';
		}
		fill( f, 0 ); f[ 0 ][ 0 ][ 0 ] = 1;
		int S = ( 1 << m - 1 ) - 1;
		for( int i = 0 ; i < n ; i++ )
			for( int j = 0 ; j <= S ; j++ )
				for( int k = 0 ; k <= i * m ; k++ ) if( f[ i ][ j ][ k ] )
				{
					inc( f[ i + 1 ][ ( j << 1 ) & S ][ k + __builtin_popcount( h[ j << 1 ] ) ], f[ i ][ j ][ k ] );
					if( i <= n - m && ok[ i + 1 ] ) dec( f[ i + 1 ][ ( j << 1 | 1 ) & S ][ k + __builtin_popcount( h[ j << 1 | 1 ] ) ], f[ i ][ j ][ k ] );
				}
		for( int i = 1 ; i <= tot ; i++ ) inc( ans, 1LL * f[ n ][ 0 ][ i ] * g[ i ] % mod );
		printf( "%d\n", ans );
	}
}

inline void solve()
{
	n = read(), m = read(); tot = 0;
	for( int i = 1 ; i <= n ; i++ )
	{
		scanf( "%s", ch + 1 ); can[ i ] = 0;
		for( int j = 1 ; ch[ j ] ; j++ ) can[ i ] |= 1 << ch[ j ] - 'a', tot++;
	}
	scanf( "%s", ch + 1 );
	bool no_ans = 0;
	for( int i = 1 ; i <= n - m + 1 ; i++ )
	{
		ok[ i ] = 1;
		for( int j = 1 ; j <= m ; j++ ) if( !( can[ i + j - 1 ] & 1 << ch[ j ] - 'a' ) ) ok[ i ] = 0;
		no_ans |= ok[ i ];
	}
	if( !no_ans ) { puts( "-1" ); return ; }
	ans = 0;
	for( int i = 1 ; i <= tot ; i++ ) a[ i ] = qpow( ( mod + 1 - 1LL * ( tot - i ) * qpow( tot, mod - 2 ) % mod ) % mod, mod - 2 ), g[ i ] = 0;
	for( int i = 1 ; i <= tot ; i++ )
		for( int j = 1 ; j <= i ; j++ )
			if( j & 1 ) dec( g[ i ], 1LL * C[ i ][ j ] * a[ j ] % mod );
			else inc( g[ i ], 1LL * C[ i ][ j ] * a[ j ] % mod );
	if( m >= 12 ) part1::solve();
	else part2::solve();
}

int main()
{
#ifdef wxh010910
	freopen( "data.in", "r", stdin );
#endif
	for( int i = 0 ; i <= 1000 ; i++ )
	{
		C[ i ][ 0 ] = 1;
		for( int j = 1 ; j <= i ; j++ ) inc( C[ i ][ j ] = C[ i - 1 ][ j - 1 ], C[ i - 1 ][ j ] );
	}
	for( int T = read() ; T ; T-- ) solve();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值