6.19 模拟赛

比赛链接

A 异或树

考虑假设我们已经确定了最高位,则可以按照最高位为 0/1 分成两类。

而最小生成树必然是两边连成一个联通块,中间再连一个边。我们可以分别算这两类贡献。

显然,最高位全是 $0$ 和最高位全是 $1$ 砍掉最高位后是一个子问题(只是少了一位),可以递归下去做。

注意 $n$ 很小,我们可以枚举一边联通块的大小(另一边就已经知道了),递归算 $ans(i,m-1)$ 。然后对于左边的所有方案和乘上右边的方案数量,对于右边的所有方案和乘上左边的方案数量,最后再乘一个组合数即可。

现在考虑中间的边的贡献。首先中间的边一定有最高位,我们直接把最高位的贡献算上($2^{m-1}$ 乘上左右的方案数量乘上组合数)。

然后现在需要考虑的问题是,有两个联通块,分别有 $x,y$ 个点,两边可以填的数不超过 $2^m-1$,询问中间可以连的最小边大小为 $mx$ 的方案个数。

发现这个不太好求,我们可以转化成求 $\ge mx$ 的方案个数,最后前缀和即可。

我们考虑,如果 $mx$ 在最高位上有值,必然一边最高位上全是 $0$ ,一边最高位上全是 $1$ 。这个情况可以直接把最高位砍掉递归下去做。

否则,如果最高位上没有值,我们考虑把左边分成是否最高位有值的两块,右边分成是否有值的两块,于是最小值一定在最高位的 $0-0$ 中产生或者在最高位 $1-1$ 中产生。然后发现这是两个子问题,递归做即可。

考虑复杂度,前面状态数是 $O(n)$ ,前面需要枚举联通块 $O(n)$ ,处理后面的中间边复杂度 $O(n^22^m)$ 于是总复杂度大概是 $O(n^42^m)$。注意到枚举的数后面两个加起来也不会超过前面枚举的数,所以有值少 $\frac 1 {4}$ 的常数,同时注意到实际需要算的要抛开最高位,又有 $\frac 1 2$ 的常数,所以其实跑得很快。

这题调着有点恼火。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
int n , m , P;
int Pow( int x , int a ) {
int ret = 1;
while( a ) {
if( a & 1 ) ret = ret * 1ll * x % P;
x = x * 1ll * x % P , a >>= 1;
}
return ret;
}

int C[MAXN][MAXN];

int f[MAXN][MAXN][MAXN][300];
int F( int x , int y , int m , int mx ) { // x , y , < 2^m , >= mx number of edges
if( mx == 0 || !x || !y ) return Pow( 1 << m , x + y );
if( x > y ) x ^= y ^= x ^= y;
int &res = f[x][y][m][mx];
if( ~res ) return res;
if( mx & ( 1 << m - 1 ) ) return res = 2 * F( x , y , m - 1 , mx ^ ( 1 << m - 1 ) ) % P;
res = 0;
rep( i , 0 , x ) rep( j , 0 , y )
res = ( res + F( i , j , m - 1 , mx ) * 1ll * F( x - i , y - j , m - 1 , mx ) % P * C[x][i] % P * C[y][j] ) % P;
return res;
}

int g[MAXN][MAXN];
int G( int n , int m ) { // 给定 n 个点,每个数不超过 2^m-1 ,所有生成树的和
if( n <= 1 || m == 0 ) return 0;
int &res = g[n][m];
if( ~res ) return res;
res = G( n , m - 1 ) * 2 % P;
rep( i , 1 , n - 1 ) {
int tmp = 0;
rep( k , 1 , ( 1 << m - 1 ) - 1 ) tmp = ( tmp + F( i , n - i , m - 1 , k ) ) % P;
int l = Pow( 1 << m - 1 , i ) , r = Pow( 1 << m - 1 , n - i );
tmp = ( tmp + G( i , m - 1 ) * 1ll * r % P + G( n - i , m - 1 ) * 1ll * l % P ) % P;
tmp = ( tmp + l * 1ll * r % P * ( 1 << m - 1 ) ) % P;
res = ( res + 1ll * tmp * C[n][i] ) % P;
}
return res;
}

void solve() {
cin >> n >> m >> P;
C[0][0] = 1;
rep( i , 1 , MAXN - 1 ) {
C[i][0] = 1;
rep( j , 1 , i ) C[i][j] = ( C[i - 1][j - 1] + C[i - 1][j] ) % P;
}
memset( g , -1 , sizeof g ) , memset( f , -1 , sizeof f );
cout << G( n , m ) << endl;
}

signed main() {
// freopen("input","r",stdin);
// freopen("fuckout","w",stdout);
// int T;cin >> T;while( T-- ) solve();
solve();
}

B 密码

乱搞做法:对所有数取 $\ln$ 后类似字符串匹配做 NTT,最后 $\exp$ 回去,复杂度正确,精度很容易暴毙。可以参考 zyw 的实现。

正解做法:考虑把每一位的最大值拿出来代表这一位。不难发现最后只会失配 $O(\log_2v)$ 次。

简要证明:考虑一次失配。如果最大值不超过 $0.5$,那么次大值以下的值都不会到 $0.5$ ,会让 $v$ 缩小一半。否则,如果最大值超过了 $0.5$ ,失配后次大值以下的值也不会到 $0.5$ ,仍然让 $v$ 缩小一半。

倍增 + Hash 判断即可。复杂度 $O(n\log m\log v)$。代码很好写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#define P 418254373
const double eps = 8e-10;
int n , m;
const int bs = 373;
double s[19][MAXN] , p[10][MAXN];
int num[MAXN] , hs[MAXN] , pw[MAXN] , ipw[MAXN] , ahs[MAXN];
char ch[MAXN];
int A[MAXN];

int Pow( int x , int a ) {
int ret = 1;
while( a ) {
if( a & 1 ) ret = ret * 1ll * x % P;
x = x * 1ll * x % P , a >>= 1;
}
return ret;
}

int geths( int* w , int l , int r ) {
return ( w[r] - w[l - 1] + P ) * 1ll * ipw[l - 1] % P;
}

void solve() {
cin >> n >> m;
rep( i , 1 , n ) {
static int t;
double mx = 0;
rep( j , 0 , 9 ) {
scanf("%d",&t);
p[j][i] = ( 1. * t / 1e9 );
if( p[j][i] > mx ) mx = p[j][i] , num[i] = j;
}
}
ipw[0] = pw[0] = 1;
rep( i , 1 , n ) pw[i] = pw[i - 1] * 1ll * bs % P , ipw[i] = Pow( pw[i] , P - 2 ) , hs[i] = ( hs[i - 1] + pw[i] * 1ll * num[i] ) % P;
scanf("%s",ch + 1);
rep( i , 1 , m ) A[i] = ch[i] - '0' , ahs[i] = ( ahs[i - 1] + pw[i] * 1ll * A[i] ) % P;
rep( i , 1 , n ) s[0][i] = p[num[i]][i];
rep( k , 1 , 18 )
rep( i , 1 , n )
if( i + ( 1 << k ) - 1 <= n ) s[k][i] = s[k - 1][i + ( 1 << k - 1 )] * s[k - 1][i];
rep( i , 1 , n - m + 1 ) {
double ans = 1; int cur = 1 , pos = i;
while( cur <= m && ans > eps ) {
for( int k = 18 ; k >= 0 ; -- k ) if( cur + ( 1 << k ) <= m && geths( hs , pos , pos + ( 1 << k ) - 1 ) == geths( ahs , cur , cur + ( 1 << k ) - 1 ) ) {
ans = ans * s[k][pos];
cur = cur + ( 1 << k );
pos = pos + ( 1 << k );
}
ans = ans * p[A[cur]][pos];
++ cur , ++ pos;
}
printf("%.10lf\n",ans);
}
}

signed main() {
// freopen("input","r",stdin);
// freopen("fuckout","w",stdout);
// int T;cin >> T;while( T-- ) solve();
solve();
}

C 排列

如果我们设左边点表示一个位置,右边点表示一个数,连边表示这个点可以选这个数,那么可以连成一个二分图,我们相当于要问正好 $k$ 个位置完美匹配的方案数量。

大概画出来就张上面这个图这样

正好 $k$ 个位置不好整,可以二项式反演成钦定 $k$ 个位置。最后二项式反演回去即可。

考虑把 $\bmod m$ 相同的值分为一类,不难发现形成了两条链。考虑一条长度为 $n$ 的链上拿 $i$ 个匹配的方案数量,这个值是是 $\binom {n-i} i$。为啥呢,考虑用 $0$ 表示一个单点,$1$ 表示匹配了的两个点。最后一种链的匹配方案一定可以写成一个 $n-i$ 长的序列,其中有 $i$ 个 $1$ 。比如,011 这个序列对应了下面这个长度为 $5$ ,选择了 $2$ 个匹配的方案

由于对于第一张图那样的二分图相当于有两个链,我们对两个链分别整个匹配的生成函数然后乘起来就是这个二分图的拿匹配的方案数量的生成函数了。但是由于剩下的位置不是不选而是任意选,所以每一项还得乘上 $(n-i)!$。

考虑一共会有 $m-n\bmod m$ 个长度为 $\lfloor \frac n m \rfloor$ 的二分图,有 $n\bmod m$ 个长度为 $\lceil \frac n m \rceil$ 的二分图。我们把这些二分图选择 $i$ 个匹配的方案数量乘起来即可算出整个图钦定 $k$ 个位置的方案数量的生成函数。

最后二项式反演,做一次差卷积即可。代码很好写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#define P 998244353
int n , m;
int A[MAXN];

int Pow( int x , int a ) {
int ret = 1;
while( a ) {
if( a & 1 ) ret = ret * 1ll * x % P;
x = x * 1ll * x % P , a >>= 1;
}
return ret;
}

int Wn[2][MAXN];
int rev[MAXN];
void getwn( int len ) {
for( int mid = 1 ; mid < len ; mid <<= 1 ) {
int w0 = Pow( 3 , ( P - 1 ) / ( mid << 1 ) ) , w1 = Pow( 3 , P - 1 - ( P - 1 ) / ( mid << 1 ) );
Wn[0][mid] = Wn[1][mid] = 1;
rep( i , 1 , mid - 1 )
Wn[0][mid + i] = Wn[0][mid + i - 1] * 1ll * w0 % P ,
Wn[1][mid + i] = Wn[1][mid + i - 1] * 1ll * w1 % P ;
}
}
void getr( int len ) {
int l = __builtin_ctz( len ) - 1;
rep( i , 0 , len - 1 ) rev[i] = ( rev[i >> 1] >> 1 ) | ( ( i & 1 ) << l );
}
void NTT( int A[] , int len , int typ ) {
rep( i , 0 , len - 1 ) if( i < rev[i] ) swap( A[i] , A[rev[i]] );
for( int mid = 1 ; mid < len ; mid <<= 1 )
for( int i = 0 ; i < len ; i += ( mid << 1 ) )
for( int j = i ; j < i + mid ; ++ j ) {
int t1 = A[j] , t2 = A[j + mid] * 1ll * Wn[typ][mid + j - i] % P;
A[j] = ( t1 + t2 > P ? t1 + t2 - P : t1 + t2 ) , A[j + mid] = ( t1 < t2 ? t1 - t2 + P : t1 - t2 );
}
if( typ == 1 ) for( int i = 0 , iv = Pow( len , P - 2 ) ; i < len ; ++ i ) A[i] = A[i] * 1ll * iv % P;
}

int h1[MAXN] , h2[MAXN] , t[MAXN];
int J[MAXN] , iJ[MAXN];

int C( int a , int b ) {
if( a < b || a < 0 || b < 0 ) return 0;
return J[a] * 1ll * iJ[b] % P * iJ[a - b] % P;
}

void init( int a[] , int t ) {
int len = 1;
while( len <= t ) len <<= 1;
getr( len ) , NTT( a , len , 0 );
rep( i , 0 , len - 1 ) a[i] = a[i] * 1ll * a[i] % P;
NTT( a , len , 1 );
}

void solve() {
cin >> n >> m;
J[0] = iJ[0] = 1;
rep( i , 1 , n ) J[i] = 1ll * J[i - 1] * i % P , iJ[i] = Pow( J[i] , P - 2 );
getwn( 1 << 18 );

int t1 = n / m , t2 = ( n + m - 1 ) / m;
rep( i , 0 , t1 ) h1[i] = C( t1 - i , i );
init( h1 , t1 );
rep( i , 0 , t2 ) h2[i] = C( t2 - i , i );
init( h2 , t2 );

int len = 1;
while( len <= n ) len <<= 1;
getr( len );
NTT( h1 , len , 0 ) , NTT( h2 , len , 0 );
rep( i , 0 , len - 1 ) h1[i] = Pow( h1[i] , m - n % m ) * 1ll * Pow( h2[i] , n % m ) % P;
NTT( h1 , len , 1 );
rep( i , 0 , n ) h1[i] = h1[i] * 1ll * J[i] % P * J[n - i] % P;
rep( i , 0 , n ) t[i] = ( n - i & 1 ) ? P - iJ[n - i] : iJ[n - i];
len = 1;
while( len <= n + n ) len <<= 1;
getr( len );
NTT( t , len , 0 ) , NTT( h1 , len , 0 );
rep( i , 0 , len - 1 ) h1[i] = h1[i] * 1ll * t[i] % P;
NTT( h1 , len , 1 );
rep( i , 0 , n ) printf("%lld\n",h1[i + n] * 1ll * iJ[i] % P);
}

signed main() {
// freopen("input","r",stdin);
// freopen("fuckout","w",stdout);
// int T;cin >> T;while( T-- ) solve();
solve();
}

文章作者: yijan
文章链接: https://yijan.co/619-mo-ni-sai/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Yijan's Blog