模拟49—「Reverse·Silhouette· Seat」
Reverse
直接bfs,卡好上下界就可以AC。
其中有很多continue,所以跑过了1e9 。
正解有很多,可以在set上二分找合法答案,也可以直接类似链表把答案串起来。
考场上想到了线段树优化但是没打,真正的原因是测样例跑的很快。
然后我把样例改了改,我以为k是一半的时候应该最慢,所以把k改成了一半。
我以为改成了一半,实际上n=1e5,我改成了5000,然后还是飞快。
我以为这玩意可以飞快的跑,所以放弃了打线段树。
还是不要相信数据,还是要相信复杂度,想到正解就应该立马打了。
Silhouette
是一道有一定技巧的计数题,考试沉迷推dp无法成功。
考试的时候把能排序,矩形的计算方式的一部分想到了,没有想直接容斥。
确实dp的可行性不高,因为我尝试了很久也没有找到一个状态能够收束很多相同状态的dp定义。
限制比较严格的计数题,可以选择其中一个限制,定义不合法然后二项式反演(数树也是一样的套路)。
定义状态为至少k个不合法,利用至少转恰好的式子球出来恰好0个不合法的方案数。
排完序之后对于一个广义的矩形,可以推出来一个柿子,就是利用全集-不合法推出来的,应该可以理解。
这里不想展开解释了,详情见土哥哥的博客
Seat
这道题很有操作,是玫瑰花精的超级加强版。
这道题考场上给的时间太少了,连基本的性质都没推出来,有点亏。
我写的是沈队的做法(%%沈队,把沈队nb打在评论上)
首先需要知道两个性质
- 不管前面的人怎么选择,选到这个人的时候离他最近的人的距离是固定的。
- 不管前面的人怎么选择,选到这个人的时候能选的空区间的长度可重集是固定的。
然后,可以根据 「离他最近的人的距离」 进行分层。
在每一层内,每个点作为候选点的概率是不变的。
这里解释一下什么是候选点,就是之前层选择完了的时候,每个点作为一个空区间的中点,就说明他是一个候选点。
本层内所有的候选点都要在这一层选完,但是,并不是所有的点都能百分百成为候选点,所以需要我们计算他成为候选点的概率。
解释一下为什么要分层计算每个点作为候选点的概率。
因为在每一层空区间长度只有两个,所以根据空区间的概率可以推出来每个点作为候选点的概率。
并且每一层选择完毕之后,我们可以进行层之间的转移。
计算的方法是一个简单的\(dp\),定义\(dp_{l,r}\)为当前层\([l,r]\)为一个空区间的概率。
转移就是枚举每一个本层的区间,分奇偶转移就好了。
这个和Cicada与排序有些类似,都是在无法直接计算答案的时候,先计算一个辅助数组,通过辅助数组来得到答案。
因为在每一层中,假设我们知道了他作为候选点的概率,那么根据选择的数,可以计算他被选择的概率。
这个操作性很高,是个很nb的技巧,无法直接计算一个东西的时候,可以通过把它拆成变的和不变的。
但nb的题不是一眼就能看出来怎么拆的,比如这题,就需要仔细观察层之间转移的性质,从而在层之间转移“不变的”。
所以,广义一点的说,把一个东西拆成一直变的的不是一直变的,从而使在合理复杂度转移不是一直变的同时,减小原问题的计算难度。
所以,如果您(或者将来在看一遍的我)看懂了以上部分,那么只需要知道在没一行中,奇数区间和偶数区间中点被选择的概率就可以了。
计算答案的一个核心就是分每个点是奇数长度区间还是偶数长度区间的中点分别计算。
因为在每一行内,所有的奇数长度区间的每个点被选择的概率和偶数长度区间每个点被选择的概率是固定的。
这个需要理解一下,就是选到当前行的时候,在本层中前面已经选择了长度为奇数/偶数的区间情况有限,我么可以通过dp算出来。
然而这些所有情况的总集都不能改变,如果当前有的奇数区间和偶数区间的个数已经知道,那么选择每个奇数长度区间中点的概率是相等的,偶数同理。
我们通过分层时候的预处理可以得到这一层有多少个奇数区间和偶数区间。
所以再做一个 \(dp\) ,计算出来当前有 \(i\) 个奇数区间, \(j\) 个偶数区间的概率。
定义 \(dp_{n,m}=1\) ,n,m是初始时候这一曾有多少个奇数/偶数长度的区间。
这个分选择了奇数长度还是偶数长度转移就行了,转移并不困难。
还有一个问题就是拿到dp数组之后如何计算没一行奇数/偶数区间中点被选择的概率。
当前 \(dp_{i,j}\) 必定是选择到了 \(n-i+m-j+1\) 行。
如果这一行选择一个奇数,那么就需要保证你选择的这个区间之前没被选择,这部分的系数是 \(\frac{i}{n}\) ,因为一共还剩\(i\) 个区间,一共 \(n\) 个区间。
还需要保证选择的是这个区间,概率是 \(\frac{1}{i+2j}\)
所以用 \(dp_{i,j}*\frac{i}{n}*\frac{1}{i+2j}\) 转移到\(n-i+m-j+1\) 行选择奇数的概率就行了,偶数同理。
这样,就可以计算出来每行的时候选择奇数/偶数区间中点的概率了,最后吧奇数偶数的贡献加起来就是答案。
有不理解的地方可以看代码,不推荐直接看。
code
#include <vector>
#include <cstdio>
#include <cstring>
#include <assert.h>
#include <algorithm>
#define R register int
#define scanf Ruusupuu = scanf
#define freopen rsp_5u = freopen
#define fre(x) freopen( #x".in" , "r" , stdin ) , freopen( #x".out" , "w" , stdout )
int Ruusupuu ;
FILE * rsp_5u ;
using namespace std ;
typedef long long L ;
typedef double D ;
const int N = ( 1 << 10 ) + 10 ;
const int G = 5e1 + 10 ;
inline int read(){
int w = 0 ; bool fg = 0 ; char ch = getchar() ;
while( ch < '0' || ch > '9' ) fg |= ( ch == '-' ) , ch = getchar() ;
while( ch >= '0' && ch <= '9' ) w = ( w << 1 ) + ( w << 3 ) + ( ch ^ 48 ) , ch = getchar() ;
return fg ? -w : w ;
}
int x , n , top , P , V , pre [N] , nxt [N] , vis [N] , len [N] ;
int po [N] , pj [N] , cj [N] , co [N] , f [N][N] , dp [N][N] ;
inline int A( int a , int b ){ return ( a + b ) >= P ? ( a + b - P ) : ( a + b ) ; }
inline int E( int a , int b ){ return ( a - b ) < 0 ? ( a - b + P ) : ( a - b ) ; }
inline int T( int a , int b ){ return ( 1ll * a * b ) - ( 1ll * a * b ) / P * P ; }
inline int qpow( int x , int b , int ans = 1 ){ for( ; b ; b >>= 1 , x = T( x , x ) ) if( b & 1 ) ans = T( ans , x ) ; return ans ; }
inline int I( int a , int b ){ return T( a , qpow( b , P - 2 ) ) ; }
vector< int > v [N] ;
struct T{ int lenj , leno , cntj , cnto ; } st [N] ;
void sc(){
x = n = read() , P = read() , V = qpow( 2 , P - 2 ) ;
vis [0] = vis [n + 1] = 1 ;
for( R i = 1 ; i <= n ; i ++ ){
memset( pre , 0 , sizeof( pre ) ) , memset( nxt , 0 , sizeof( nxt ) ) ;
for( R j = 1 , last = 0 ; j <= n ; j ++ )
pre [j] = last , last = ( ( vis [j] ) ? j : last ) ;
for( R j = n , last = n + 1 ; j ; j -- )
nxt [j] = last , last = ( ( vis [j] ) ? j : last ) ;
int est = 0 , pos = 0 ;
for( R j = 1 ; j <= n ; j ++ ) if( !vis [j] ) {
int lenthis = min( j - pre [j] , nxt [j] - j ) ;
if( lenthis > est ) est = lenthis , pos = j ;
} assert( pos ) , vis [pos] = 1 ;
len [est] ++ , v [est].push_back( nxt [pos] - pre [pos] - 1 ) ;
} for( R i = n ; i ; i -- ) if( len [i] ){
top ++ ;
for( R j = 0 ; j < v [i].size() ; j ++ ){
if( v [i][j] & 1 ){
if( !st [top].lenj ) st [top].lenj = v [i][j] , st [top].cntj = 1 ;
else assert( st [top].lenj == v [i][j] ) , st [top].cntj ++ ;
} else{
if( !st [top].leno ) st [top].leno = v [i][j] , st [top].cnto = 1 ;
else assert( st [top].leno == v [i][j] ) , st [top].cnto ++ ;
}
} if( i == 1 ) st [top].lenj = 1 , st [top].leno = 0 , st [top].cntj += st [top].cnto , st [top].cnto = 0 ;
}
}
void work(){
f [1][n] = 1 ;
for( R i = 1 ; i <= top ; i ++ ){
memset( pj , 0 , sizeof( pj ) ) , memset( po , 0 , sizeof( po ) ) ;
if( i == top ){
for( R i = 1 ; i <= n ; i ++ ) for( R j = i ; j <= n ; j ++ ){
if( !f [i][j] ) continue ;
int len = ( j - i + 1 ) ;
if( len != 2 ) continue ;
f [i][i] = A( f [i][i] , f [i][j] ) , f [j][j] = A( f [j][j] , f [i][j] ) ;
} for( R i = 1 ; i <= n ; i ++ ) pj [i] = f [i][i] ;
} else{
for( R j = 1 ; j <= n ; j ++ ) for( R k = j ; k <= n ; k ++ ){
if( !f [j][k] ) continue ;
int len = ( k - j + 1 ) ;
if( len != st [i].lenj && len != st [i].leno ) continue ;
if( len & 1 ){
f [j][( ( j + k ) >> 1 ) - 1] = A( f [j][( ( j + k ) >> 1 ) - 1] , f [j][k] ) ;
f [( ( j + k ) >> 1 ) + 1][k] = A( f [( ( j + k ) >> 1 ) + 1][k] , f [j][k] ) ;
pj [( j + k ) >> 1] = A( pj [( j + k ) >> 1] , f [j][k] ) ;
} else{
int zd1 = ( j + k ) >> 1 , zd2 = ( j + k + 1 ) >> 1 ;
assert( zd1 != zd2 ) ;
f [j][zd1 - 1] = A( f [j][zd1 - 1] , T( V , f [j][k] ) ) ;
f [zd1 + 1][k] = A( f [zd1 + 1][k] , T( V , f [j][k] ) ) ;
f [j][zd2 - 1] = A( f [j][zd2 - 1] , T( V , f [j][k] ) ) ;
f [zd2 + 1][k] = A( f [zd2 + 1][k] , T( V , f [j][k] ) ) ;
po [zd1] = A( po [zd1] , T( V , f [j][k] ) ) ;
po [zd2] = A( po [zd2] , T( V , f [j][k] ) ) ;
}
}
}
memset( dp , 0 , sizeof( dp ) ) , dp [st [i].cntj][st [i].cnto] = 1 ;
memset( cj , 0 , sizeof( cj ) ) , memset( co , 0 , sizeof( co ) ) ;
for( R j = st [i].cntj ; ~j ; j -- ) for( R k = st [i].cnto ; ~k ; k -- ){
if( j ) dp [j - 1][k] = A( dp [j - 1][k] , T( I( j , j + 2 * k ) , dp [j][k] ) ) ;
if( k ) dp [j][k - 1] = A( dp [j][k - 1] , T( I( 2 * k , j + 2 * k ) , dp [j][k] ) ) ;
int now = st [i].cntj - j + st [i].cnto - k + 1 ;
cj [now] = A( cj [now] , T( dp [j][k] , T( I( j , st [i].cntj ) , I( 1 , j + 2 * k ) ) ) ) ;
co [now] = A( co [now] , T( dp [j][k] , T( I( k , st [i].cnto ) , I( 2 , j + 2 * k ) ) ) ) ;
}
for( R j = 1 ; j <= st [i].cntj + st [i].cnto ; j ++ , puts( "" ) )
for( R k = 1 ; k <= n ; k ++ ) printf( "%d " , A( T( po [k] , co [j] ) , T( pj [k] , cj [j] ) ) ) ;
}
}
signed main(){
// fre( in ) ;
sc() ;
work() ;
return 0 ;
}
反思
新学期第一场考试,太爆炸了。
T1手残少按一个0,数据强点就TLE了,想到正解就应该打,复杂度不对就别想着投机取巧。
T2会34分的状压dp就应该调处来,分数比什么都重要,即使沉迷思考会无法自拔。
所以本场分数上限有可能是 100 + 34 + 0
实际有可能爆炸成 40 + 0 + 0
还是要先把暴力打完在思考,需要赶紧扶正摇摇欲坠的flag

浙公网安备 33010602011771号