「PER #2」2048

题目

点这里看题目。

Public Judge 是新出来的 OJ,所以可能认不得。

不过没关系,根据 p_b_p_b 的说法,上面的题基本上都是搬的

分析

首先,可以忽略每次加入的“位置”,因为我们始终可以保证所有的数都排在前缀上。

其次,我们注意到,如果新来的数大于当前最靠后的那个数,则最终效果是原有前缀全部被丢弃;否则,我们如果保证新来的数总是小于等于最靠后的那个数,则最终效果是进行二进制的累加

所以,对于当前的一个活跃(即会影响累加结果)的前缀而言,它一定是单调递减的。这样的话,只要不进位,我们可以认为已有的数对于之后的数没有后效性。根据这个性质,我们可以将这个前缀的形成过程,看作是每次经过若干步,在这个前缀的末尾加入了一个新的数

那么,首先考虑一个数出现的概率。由于没有步数限制,我们只需要记录 \(f_{i,j}\),表示长度为 \(i\) 的序列中,经过若干步,在头部产生一个 \(2^j\) 的概率。容易得到它的转移方程:

\[f_{i,j}=[j \le m]p_{j+1}+f_{i,j-1}f_{i-1,j-1} \]

转移的逻辑是,要么是直接生成 \(2^j\),要么先后生成两个 \(2^{j-1}\)(后面的生成过程不会受前面的那个影响),最后再合并。

而后,利用期望的线性性拆开贡献,则我们只需要考虑某个前缀的出现概率,直接一边构造前缀形态一边 DP。设 \(g_{i,j}\) 表示经过若干步后,生成一个长度为 \(i\) 的前缀,以 \(2^j\) 结尾的概率。倒腾一下也可以得到转移方程:

\[g_{i,j}=\sum_{k>j}g_{i-1,k}f_{n-i+1,j}+\sum_{k<j}g_{i-1,k}\prod_{s=k+1}^jp_s\prod_{t=s}^{j-1}f_{n-i,t} \]

转移逻辑是,分类讨论前缀 \([1,i-1]\)

  1. 如果 \(i-1\) 上的数大于 \(2^j\),则前缀 \([1,i-1]\) 不会影响第 \(i\) 个数的产生,直接用 \(f\) 计算;

  2. 如果 \(i-1\) 上的数小于 \(2^j\),根据之前的分析,第 \(i\) 个数的初始值 \(2^s\) 必须大于第 \(i-1\) 个,才能让前缀 \([1,i-1]\) 失效;之后,还需要从小到大产生若干个数,来将 \(2^s\) 提升到 \(2^j\)

    这一部分其实比较类似于限制第一个数的 \(f\) 的转移。

朴素转移为 \(O(n^3)\),不难优化到 \(O(n^2)\)

小结:

  1. 这道题提供了一个基础思路:操作过程比较复杂的模型,可以将连续的操作过程拆分成若干个步骤来考虑

  2. 另外一方面,既然操作过程很复杂,也应该抓住较为简洁的“生成的前缀”这一部分来考虑。

代码

#include <cstdio>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

const int mod = 998244353;
const int MAXN = 4005;

template<typename _T>
void read( _T &x ) {
	x = 0; char s = getchar(); bool f = false;
	while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	if( f ) x = -x;
}

template<typename _T>
void write( _T x ) {
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) write( x / 10 );
	putchar( x % 10 + '0' );
}

int f[MAXN][MAXN], g[MAXN][MAXN];

int pw[MAXN];
int P[MAXN];

int N, M;

inline int Qkpow( int, int );
inline int Inv( const int &a ) { return Qkpow( a, mod - 2 ); }
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }

inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }

inline int Qkpow( int base, int indx ) {
	int ret = 1;
	while( indx ) {
		if( indx & 1 ) MulEq( ret, base );
		MulEq( base, base ), indx >>= 1;
	}
	return ret;
}

int main() {
	int su = 0;
	read( N ), read( M );
	rep( i, 0, M - 1 ) read( P[i] ), AddEq( su, P[i] );
	su = Inv( su );
	rep( i, 0, M - 1 ) MulEq( P[i], su );
	rep( i, 1, N ) rep( j, 0, N + M ) {
		if( j < M ) f[i][j] = P[j];
		if( j > 0 ) AddEq( f[i][j], Mul( f[i][j - 1], f[i - 1][j - 1] ) );
	}
	int ans = 0; g[0][N + M] = 1;
	pw[0] = 1; rep( i, 1, N + M + 1 ) pw[i] = Mul( pw[i - 1], 2 );
	rep( i, 1, N ) {
		int su = 0, contri = 0;
		per( j, N + M, 0 ) {
			AddEq( g[i][j], Mul( su, f[N - i + 1][j] ) );
			AddEq( su, g[i - 1][j] );
		}
		su = g[i - 1][0];
		rep( j, 1, N + M ) {
			MulEq( contri, f[N - i][j - 1] );
			AddEq( contri, Mul( su, P[j] ) );
			AddEq( g[i][j], contri );
			AddEq( su, g[i - 1][j] );
		}
		rep( j, 0, N + M )
			AddEq( ans, Mul( g[i][j], Mul( f[N - i][j], pw[j + 1] ) ) );
	}
	write( ans ), putchar( '\n' );
	return 0;
}
posted @ 2022-05-08 19:50  crashed  阅读(67)  评论(0编辑  收藏  举报