GZM考试题

ENDER 太强了 %%%%%% orz

一.wait

题目描述

题目描述 有一的棋盘,每次随机染黑一个位置(可能染到已经黑了的),当某一行或者一列全为黑色时停止,求期望染色次数()

输入格式

一行两个正整数

输出格式

期望结果

样例

样例输入1

2 2

样例输出1

3

样例输入2

10 20

样例输出2

397903748

n <= 1000

又是一道数论题.....mmp

前置知识(芝士)

min-max反演

还是安利博客吧,插入公式用不了

分析

好了,但是好像这道题跟反演没有太大关系,需要把题意变一下

令a[i]表示第i行最多需要通过多少步来填满

b[i]表示第i列最多需要通过多少步来填满

那么就有一个集合:S{ a ,b }

那么现在就要算期望值了

min(S)=∑T⊆S(−1)|T|+1max(T)

也就是从T就是从a里面随机选i个,b里面从中选j个组成的集合

那么需要染成的格子有: i*m+j*n-i*j

推理:如果只有一个格子需要染色,那么其期望就是 nm

如果有两个格子需要染色,那么期望就是nm/2+nm,先染一个,再染一个

那么k个格子就是nm(1 + 1/2 + 1/3 +....+k/1)

由于条件有限,所以格式不好见谅

那么就可以用这个算出来max(T),min(S)也可以了

代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long
const int MAXN = 1000 * 1003;
const ll mod = 998244353;
ll n , m;
ll niv[MAXN] , sum[MAXN] , fac[MAXN];
ll C ( ll x , ll y ){
    return fac[x] * niv[y] % mod * niv[x-y] % mod;
}
int main(){
    scanf( "%lld%lld" , &n , &m );
    niv[0] = niv[1] = 1;
    sum[1] = 1ll * n * m;
    fac[0] = fac[1] = 1;
    for( ll i = 2 ; i <= n * m ; i ++ ){
        fac[i] = fac[i-1] * i % mod;
        niv[i] = niv[mod%i] * ( mod - mod / i ) % mod;
        sum[i] = (sum[i-1] + n * m * niv[i] % mod ) % mod;
    }
    for( int i = 1 ; i <= n * m ; i ++ ){
        //fac[i] = ( fac[i] * fac[i-1] ) % mod;
        niv[i] = niv[i-1] * niv[i] % mod;
    }
    ll ans = 0;
    for( ll i = 0 ; i <= n ; i ++ ){
        for( ll j = 0 ; j <= m ; j ++ ){
            if( i || j ){
                int a = i * m + j * n - i * j;
                ll t = C( n , i ) * C( m , j ) % mod * sum[a] % mod;
                if( ( i + j + 1 ) % 2 == 0 )
                    ans = ( ans + t ) % mod;
                else
                    ans = ( ans - t  + mod ) % mod;
            }
        }
    }
    printf( "%lld" , ans );
}

 

二.游戏

题目描述

双方进行游戏,有两个正整数a,b 若a<=b 双方轮流进行选择下列两种操作之一 1)将b对a取模,即b变为b%a 2)从b中减去a的幂,不能减成负数,即b变为b-a^k(b-a^k>=0且k为正整数) 若a>b,类似 若a,b中之一为0,则无法进行,无法进行者败

输入初始a,b,判断先后手谁有必胜策略

输入格式

第一行一个正整数T 接下来T行每行两个正整数a,b

输出格式

对于每个数据输出"First"或"Second"表示先手必胜或后手必胜

样例

样例输入1:

4
10 21
31 10
0 1
10 30

样例输出1:

First
Second

数据范围与提示

样例解释

在样例一中,第一个玩家只能变换到(11,10)。然后在第二个玩家变换到(1,10)后,第一个玩家可以将10对1取余就赢了。

在样例二中,第一个玩家可以变换到(1,10)或(21,10),不管哪种,第二个玩家都赢。

在样例三中,第一个玩家无法操作。

在样例四中,第一个玩家直接对30进行取余,就赢了。

a , b <= 1e18 , T <=1e3

分析

不妨设b>a

把b拆成 b = ak+c ( c == b % a)

把先手能赢表示1,反之为0

可以玄学找到如果k位偶数的循环节 有 a + 1 个位一个循环

如果k为奇数 那么循环节为1 0 

那么第二种情况就解决了

再看第一种,如果给他b%a,a是先手必败,那么First一定是先手必胜

就可以了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long
const int MAXN = 1000 * 1003;
ll a , b;
int t;
bool check( ll a , ll b ){
    if( !a ) return 0;
    if( !check( b % a , a ) ) return 1;
    ll k = (( b - b % a ) / a ) % ( a + 1 );
    if( k == 0 || ((k & 1) ^ 1) )
        return 1;
    return 0;
}
int main(){
    scanf( "%d" , &t );
    while( t -- ){
        scanf( "%lld %lld" , &a , &b );
        if( a < b ) swap( a , b );
        if( check( b , a ) )
            printf( "First\n" );
        else
            printf( "Second\n" );

    }
}

三. R

题目描述

题目描述 有两个1~n的排列A,B,序列C一开始为空,每次可以选择进行以下两种操作之一 1)若A不为空,则可取出A的开头元素放在序列C的末尾 2)若B不为空,则可取出B的开头元素放在序列C的末尾 这样当A,B皆为空时,C称为排列A,B的合并,其长度为2*n 记F(A,B)为A,B的所有可能合并的总数 求对于所有可能的1~n的排列A,B,F(A,B)的和,mod 998244353

样例

样例输入1

2

样例输出1

12

样例输入2

10

样例输出2

n <= 100

分析

毒瘤dp题

dp[i][j][k]表示A数组用了i个,B数组用了j个,它们有k个是重复的排列

那么对于一个数,它就有四种情况:

1.在i中出现过,但没在j中出现   这样的数有 i-k个 对 dp[i][j+1][k+1]

2.在j中出现过,但是没在i中出现 这样的数有 j - k 对于 dp[i+1][j][k+1]

3.在i,j中都没出现过,有n-(i+j-k)个 对于 dp[i+1][j][k]

4.在i,j中都没出现过,有n-(i+j-k)个 对于 dp[i][j+1][k]

这样就可以实现简单的dp转移

但是有一个问题,它会有重复的情况,很好的例子 1 1

这样手推算出来有两种,显然只有一种

令g[i]是由两个相同的子排列组成的没有比他更小的前缀也满足这样的条件

比如 11 是g[1]中的

123231是g[3]中的 , 但是122133是不合法的,因为1221是g[2]中的排列

那么去重就是

dp[i][j][k]−=∑kd=1Cdn−(i−d+j−d−(k−d))∗f[i−d][j−d][k−d]∗g[d]∗(d!)f[i][j][k]−=∑d=1kCn−(i−d+j−d−(k−d))d∗dp[i−d][j−d][k−d]∗g[d]∗(d!) 

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long
const ll mod = 998244353;
const int MAXN = 203;
ll fac[MAXN] , dp[MAXN][MAXN][MAXN];
ll g[MAXN] , ni[MAXN] , ni2;
ll n;
ll C( ll x , ll y ){
    return fac[x] * ni[y] % mod * ni[x-y] % mod;
}
ll max_( ll x , ll y ){
    if( x > y )
        return x;
    return y;
}
ll min_( ll x , ll y ){
    if( x < y )
        return x;
    return y;
}
int main(){
    scanf( "%lld" , &n );
    fac[0] = fac[1] = ni[0] = ni[1] = 1;
    for( ll i = 2 ; i <= n * 2 ; i ++ ){
        fac[i] = fac[i-1] * i % mod;
        ni[i] = ni[mod%i] * ( mod - mod / i ) % mod;
    }
    for( int i = 1 ; i <= n * 2 ; i ++ ){
        ni[i] = ni[i-1] * ni[i] % mod;
    }
    g[0] = 1;
    for( ll i = 1 ; i <= n ; i ++ ){
        g[i] = C( 2 * i , i );
        for( ll j = 1 ; j < i ; j ++ )
            g[i] = ( g[i] + mod - C( 2 * ( i - j ) , i - j ) * g[j] % mod * 2 % mod ) % mod;
        g[i] = g[i] * ni[2] % mod;
    }
    dp[0][0][0] = 1;
    for( ll i = 0 ; i <= n ; i++ ){
        for( ll j = 0 ; j <= n ; j ++ ){
            for( ll k = max_( 0 , i + j - n ) ; k <= min_( i , j ) ; k ++ ){
                if( !i && !j ) break;
                ll &ans = dp[i][j][k];
                if( i && k ) ans = ( ans + dp[i-1][j][k-1] * (j - k + 1 ) % mod ) % mod;
                if( i > 0 ) ans = ( ans + dp[i-1][j][k] * ( n - ( i + j - 1 - k ) % mod ) ) % mod;
                if( j > 0 ) ans = ( ans + dp[i][j-1][k] * ( n - ( i + j - 1 - k ) % mod ) ) % mod;
                if( j > 0 && k > 0 ) ans = ( ans + dp[i][j-1][k-1] * ( i - k + 1 ) % mod ) % mod;
                for( int l = 1 ; l <= k ; l ++ )
                    ans = ( ans + mod - g[l] * fac[l] % mod * dp[i-l][j-l][k-l] % mod * C( n - ( i + j - k - l ) , l ) % mod ) % mod;
            }
        }
    }
    printf( "%lld" , dp[n][n][n] );
}
发布了68 篇原创文章 · 获赞 7 · 访问量 3850

猜你喜欢

转载自blog.csdn.net/weixin_43823476/article/details/97479357