GZM2 模拟赛 Day26

wait

一、题目及数据范围

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

二、解法

期我打模拟退火都骗不了分,期望的题都很毒瘤。
我们需要这样转化问题:
假设涂色时在涂完某一行或某一列是停止,则可以设 { a i } \{ai\} {ai} ( 1 ≤ i ≤ n ) (1\le i\le n) (1in) 为第i行全部涂色的时间, { b i } \{bi\} {bi} ( 1 ≤ i ≤ m ) (1\le i \le m) (1im),为第i列全部涂色的时间。
U = { a i } ∪ { b i } U=\{ai\}\cup \{bi\} U={ai}{bi},答案为ans,则可以得到 a n s = m i n { v ∣ v ⊆ U } ans=min\{v\vert v \subseteq U \} ans=min{vvU}。这里的都是期望值。
又发现做不动了,怎么办呢?

是时候介绍我们的Min-Max反演!
m i n { v ∣ v ⊆ U } = ∑ S ⊆ U ∧ S ≠ ∅ ( − 1 ) s + 1 m a x { t ∣ t ⊆ S } min\{v\vert v \subseteq U \}=\sum_{S\subseteq U \wedge S\not = \emptyset}(-1)^{s+1}max\{t\vert t\subseteq S \} min{vvU}=SUS=(1)s+1max{ttS}
要证明这个是正确的,我们只需要保证右边这一坨中除了最小值以外,其它的都被消掉了。
我们先对 U U U 里面的元素排序。然后,对于 U U U 中排序后的第 i i i 个元素 u i ui ui​ ,讨论:
1. i = 1 i=1 i=1 ,此时会留下一个 u 1 u1 u1
2. i ≠ 1 i\not = 1 i=1 ,则可以确定,计算到 u i ui ui 的子集中只会有小于等于 u i ui ui 的元素。实际上就是 u 1 u1 u1 u i − 1 ui−1 ui1​ 这个集合的子集并上 u i ui ui​ ,共 2 i − 1 2^{i-1} 2i1;其中集合大小为奇数的有 2 i − 2 2^{i-2} 2i2 个,为偶数的有 2 i − 2 2^{i-2} 2i2 个,抵消之后不会被计入答案。
所以该反演是正确的。

回到原题,我们可以用反演将式子变形。
a n s = m i n { v ∣ v ⊆ U } = ∑ S ⊆ U ∧ S ≠ ∅ ( − 1 ) s + 1 m a x { t ∣ t ⊆ S } ans=min\{v\vert v \subseteq U \}=\sum_{S\subseteq U \wedge S\not = \emptyset}(-1)^{s+1}max\{t\vert t\subseteq S \} ans=min{vvU}=SUS=(1)s+1max{ttS}
根据期望的线性(可加性),可得
E ( a n s ) = E ( m i n { v ∣ v ⊆ U } ) = E ( ∑ S ⊆ U ∧ S ≠ ∅ ( − 1 ) s + 1 m a x { t ∣ t ⊆ S } ) E(ans)=E(min\{v\vert v \subseteq U \})=E(\sum_{S\subseteq U \wedge S\not = \emptyset}(-1)^{s+1}max\{t\vert t\subseteq S \}) E(ans)=E(min{vvU})=E(SUS=(1)s+1max{ttS})
那么问题就转化成了求 E ( ∑ S ⊆ U ∧ S ≠ ∅ ( − 1 ) s + 1 m a x { t ∣ t ⊆ S } ) E(\sum_{S\subseteq U \wedge S\not = \emptyset}(-1)^{s+1}max\{t\vert t\subseteq S \}) E(SUS=(1)s+1max{ttS})
这就相当于选 i i i j j j列,求把这里面包含的格子染色的期望时间。也就是染色 i ∗ m + j ∗ n − i ∗ j i*m+j*n-i*j im+jnij个格子的期望时间。设 k k k为需要染色的格子总数:
k = 1 k=1 k=1时, E = m ∗ n E=m*n E=mn(即染 m ∗ n m*n mn次期望能染到那个格子)
k = 2 k=2 k=2时, E = m ∗ n + m ∗ n / 2 E=m*n+m*n/2 E=mn+mn/2(即先花 m ∗ n / 2 m*n/2 mn/2次染一个格子,就变成了 k = 1 k=1 k=1,期望相加)
以此类推…
我们可以发现涂 k k k个格子的期望为: E = ∑ i = 1 k n ∗ m / i E=\sum_{i=1}^{k} n*m/i E=i=1knm/i
然后就对这个反演进行求和,注意统计本质不同的方案数,时间复杂度 O ( n ∗ m ) O(n*m) O(nm),请看代码。

#include <cstdio>
const int MOD = 998244353;
const int MAXN = 1005;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,m,ans,fac[MAXN],finv[MAXN],inv[MAXN*MAXN],f[MAXN*MAXN];
int max(int a,int b)
{
    return a>b?a:b;
}
int C(int a,int b)
{
    return 1ll*fac[b]*finv[a]%MOD*finv[b-a]%MOD;
}
int main()
{
    n=read();
    m=read();
    inv[1]=1;
    f[1]=n*m;
    for(int i=2; i<=n*m; i++)
    {
        inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
        f[i]=(f[i-1]+1ll*n*m*inv[i]%MOD)%MOD;
    }
    finv[0]=fac[0]=1;
    for(int i=1; i<=max(n,m); i++)
    {
        fac[i]=1ll*fac[i-1]*i%MOD;
        finv[i]=1ll*inv[i]*finv[i-1]%MOD;
    }
    for(int i=0; i<=n; i++)
        for(int j=0; j<=m; j++)
            if(i|j)
            {
                int a=i*m+j*n-i*j;
                int t=1ll*C(i,n)*C(j,m)%MOD*f[a]%MOD;
                ans=(1ll*ans+t*((i+j)&1?1:-1)+MOD)%MOD;
            }
    printf("%d\n",ans);
}

游戏

一、题目及数据范围

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

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

二、解法

UPD 2020-8-24:把这道神题重新梳理一下

不妨设 a < b a<b a<b,由于我们有两种操作,而且 s g sg sg函数也不好定义,我们干脆先只考虑第一种操作,然后再结合第二种操作。我们写的是 d f s dfs dfs,如果无脑辗转相除返回的是先手必输的话,那么当前的先手就赢了,直接返回。现在要考虑辗转相除后先手必胜的情况,也就是状态 { a , b % a } \{a,b\% a\} {a,b%a}是必胜态,我们想知道 { a , b } \{a,b\} {a,b}的状态。

想一想它和什么东西有关系,我们可以把要求的状态转移到 { a , b % a } \{a,b\%a\} {a,b%a}去,转移的过程应该是只和 a b \frac{a}{b} ba,从他入手!

k = a b k=\frac{a}{b} k=ba,设 f ( k ) = 0 / 1 f(k)=0/1 f(k)=0/1为先手必输还是先手必胜的函数,我们已经知道了 f ( 0 ) = 1 f(0)=1 f(0)=1,转移就是 f ( k ) = f ( k − a i ) f(k)=f(k-a^i) f(k)=f(kai),你首先需要知道一个优美的结论: a i = { 1 , a } m o d    ( a + 1 ) a^i=\{1,a\}\mod (a+1) ai={1,a}mod(a+1),神奇的地方在于两种余数之和等于模数

不要急,我们先来看 k ≤ a k\leq a ka的情况:

  • 对于 k ≠ a k\not=a k=a,只能从 k − 1 k-1 k1转移,那么 f ( k ) = ! f ( k − 1 ) f(k)=!f(k-1) f(k)=!f(k1)
  • 对于 k = a k=a k=a,由于 0 0 0已经是先手必胜了,拿他的状态肯定先手必败,那么 f ( k ) = ! f ( k − 1 ) f(k)=!f(k-1) f(k)=!f(k1)

这类情况就很好判断了(只用模 2 2 2的余数),打表发现 一个结论: f ( k ) = f ( k m o d    ( a + 1 ) ) f(k)=f(k\mod(a+1)) f(k)=f(kmod(a+1)),证明(多看几遍看上面加粗的部分,下面取几的含义是取的东西的余数):

  • f ( k m o d    ( a + 1 ) ) = 0 f(k\mod(a+1))=0 f(kmod(a+1))=0,那么后手一定让你拿到那个状态,先手无法反制。
  • f ( k m o d    ( a + 1 ) ) = 1 f(k\mod(a+1))=1 f(kmod(a+1))=1,由于 f ( k m o d    ( a + 1 ) − 1 ) = 0 f(k\mod(a+1)-1)=0 f(kmod(a+1)1)=0,那么先手先取一个 1 1 1在给后手,后手无法反制地拿到了必败状态。
  • 需要特别考虑 k m o d    ( a + 1 ) = 0 k\mod(a+1)=0 kmod(a+1)=0(总不能减成负数吧),先手取一个 a a a,后手取到 f ( k − a ) f(k-a) f(ka)就输掉了(因为 k − a m o d    ( a + 1 ) = 1 k-a\mod(a+1)=1 kamod(a+1)=1,那么就转移到了 f ( 1 ) f(1) f(1),也就是后手必败)

这真的是人能想出来的?但是代码却是人能写出来的

#include <cstdio>
#include <iostream>
#define LL long long
using namespace std;
const LL MOD = 998244353;
LL read()
{
    LL x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
LL T,a,b;
LL DFS(LL a,LL b)
{
    if(a>b) swap(a,b);
    if(a==0) return 0;
    if(!DFS(b%a,a)) return 1;
    LL k=(b/a)%(a+1);
    return (k&1)==0;
}
int main()
{
    T=read();
    while(T--)
    {
        a=read();
        b=read();
        if(DFS(a,b))
            puts("First");
        else
            puts("Second");
    }
}

R

一、题目及数据范围

题目描述 有两个 1 ∼ n 1\sim n 1n的排列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

二、解法

设计 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]为填第一个数列填 i i i个,第二个数列填 j j j个,有 k k k个数重复的方案数。
我们很轻易的能写出下列转移:

int &s=dp[i][j][k];
if(i&&k) s=(s+1ll*dp[i-1][j][k-1]*(j-k+1))%MOD;
if(j&&k) s=(s+1ll*dp[i][j-1][k-1]*(i-k+1))%MOD;
if(i) s=(s+1ll*dp[i-1][j][k]*(n-i-j+k+1))%MOD;
if(j) s=(s+1ll*dp[i][j-1][k]*(n-i-j+k+1))%MOD;

难道这道题真的只有这么简单吗?肯定不会。
我们考虑对方案数去重,我们选出 d d d个不同的重复数,这样的 C ( n − i − j + k + d , d ) C(n-i-j+k+d,d) C(nij+k+d,d),即除了序列中单独出现的数都可以选,再对这 d d d个数全排,乘上 d p [ i − d ] [ j − d ] [ j − d ] dp[i-d][j-d][j-d] dp[id][jd][jd]。最后要计算这 2 d 2d 2d个数能生成的序列个数,如果直接用 C ( 2 d , d ) C(2d,d) C(2d,d),肯定会多去重。我们定义好串为不满足 [ 1 , i ] [1,i] [1,i]中每个数都出现两次的生成串,我们自然想起了在一个 n ∗ n n*n nn的矩形,从左下角到右上角(把移动看成取数),不经过对角线的方案数(我们这里要将对角线向下平移一格,因为我们不能取到对角线),我们就用 C ( 2 d , d ) − C ( 2 d , d + 1 ) C(2d,d)-C(2d,d+1) C(2d,d)C(2d,d+1)计算数量。
下面看代码吧。

#include <cstdio>
const int MOD = 998244353;
const int MAXN = 105;
int read()
{
    int x=0,flag=1;
    char c;
    while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    return x*flag;
}
int n,fac[MAXN*2]= {1},inv[MAXN*2]= {1,1};
int dp[MAXN][MAXN][MAXN];
int max(int a,int b)
{
    return a>b?a:b;
}
void init()
{
    for(int i=2; i<=n*2; i++)
        inv[i]=1ll*(MOD-MOD/i)*inv[MOD%i]%MOD;
    for(int i=1; i<=n*2; i++)
    {
        fac[i]=1ll*fac[i-1]*i%MOD;
        inv[i]=1ll*inv[i]*inv[i-1]%MOD;
    }
}
int C(int n,int m)
{
    return 1ll*fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int ctl(int x)
{
    if(x==0) return 1;
    return C(x<<1,x)-C(x<<1,x+1);
}
int g(int x)
{
    return ctl(x-1);
}
int main()
{
    n=read();
    init();
    dp[0][0][0]=1;
    for(int i=0; i<=n; i++)
        for(int j=0; j<=n; j++)
            if(i|j)
                for(int k=max(0,i+j-n); k<=i && k<=j; k++)
                {
                    int &s=dp[i][j][k];
                    if(i&&k) s=(s+1ll*dp[i-1][j][k-1]*(j-k+1))%MOD;
                    if(j&&k) s=(s+1ll*dp[i][j-1][k-1]*(i-k+1))%MOD;
                    if(i) s=(s+1ll*dp[i-1][j][k]*(n-i-j+k+1))%MOD;
                    if(j) s=(s+1ll*dp[i][j-1][k]*(n-i-j+k+1))%MOD;
                    for(int d=1; d<=k; d++)
                        s=(s+MOD-1ll*dp[i-d][j-d][k-d]*C(n-i-j+k+d,d)%MOD*g(d)%MOD*fac[d]%MOD)%MOD;
                }
    printf("%d\n",dp[n][n][n]);
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值