Codeforces 题面传送门 & 洛谷题面传送门

真·两天前刚做过这场的 I 题,今天模拟赛就考了这场的 H 题,我怕不是预言带师

提供一种奇怪的做法,来自于同机房神仙们,该做法不需要 Min-Max 容斥,也不用爆推组合数,只需要比较强的眼力的初中数学求解二元一次方程组知识。

期望题没往 Min-Max 容斥的方向去想,不愧是我(大雾

首先我们先考虑一些复杂度比较高的多项式复杂度做法。注意到对于任何一个局面而言,我们并不用关心 \(S\) 里究竟具体有哪些数,也不用关心牌堆中具体有哪些数字牌,我们只用关心有多少数在 \(S\) 中,以及牌堆中还剩多少张牌。因此我们可以设 \(dp_{i,j}\) 表示牌堆中还剩 \(i\) 张牌,\(S\) 中已经有 \(j\) 个数,期望还需多少步,那么:

  • 对于 \(j\ne n\),下一次摸出一张牌,有三种可能:

    • 摸出一张数字牌,且不在 \(S\) 中,概率 \(\dfrac{n-j}{i+m}\),并且会到达状态 \(dp_{i-1,j+1}\),因此 \(dp_{i,j}\leftarrow dp_{i-1,j+1}·\dfrac{n-j}{i+m}\)
    • 摸出一张数字牌,且在 \(S\) 中,概率 \(\dfrac{i+j-n}{i+m}\),并且会到达状态 \(dp_{i-1,j}\),因此 \(dp_{i,j}\leftarrow dp_{i-1,j}·\dfrac{i+j-n}{i+m}\)
    • 摸出一张鬼牌,概率 \(\dfrac{m}{i+m}\),并且会到达状态 \(dp_{n,j}\),因此 \(dp_{i,j}\leftarrow dp_{n,j}·\dfrac{m}{i+m}\)。

    别忘了加上最后的 \(1\),因此对于 \(j\ne 0\) 的情况我们有转移方程 \(dp_{i,j}=dp_{i-1,j+1}·\dfrac{n-j}{i+m}+dp_{i-1,j}·\dfrac{i+j-n}{i+m}+dp_{n,j}·\dfrac{m}{i+m}+1\)。

  • 对于 \(j=n\)​ 的情况就比较 trivial 了,如果摸出一张数字牌那么会到达 \(dp_{i-1,j}\)​,否则直接结束,因此 \(dp_{i,n}=dp_{i-1,n}·\dfrac{i}{i+m}+1\)​

最终答案即为 \(dp_{n,0}\)

由于 DP 转移存在后效性,因此直接转移不可取,考虑高斯消元,直接高斯消元是六方的,不过注意到对于一个 \(dp_{i,j}\) 而言,如果我们倒着枚举 \(j\),那么我们就只用对 \(j\) 相同的这一行的值进行高斯消元,复杂度 \(n^4\)。还可以进一步优化,就是注意到在我们倒序枚举的过程中 \(dp_{i-1,j+1}·\dfrac{n-j}{i+m}\) 是常数不用管它,如果我们不考虑这个 \(dp_{n,j}\) 那转移关系不成环就不存在后效性,而加上这个 \(dp_{n,j}\),由于导致这个后效性的只有 \(dp_{n,j}\),我们就可以考虑将所有 \(dp_{i,j}\) 都表示成 \(sdp_{n,j}+t\) 的形式,这样顺着一遍推过去,最后可以得到 \(dp_{n,j}=sdp_{n,j}+t\),解出 \(dp_{n,j}\) 后再推回去即可,这个套路可以在这道题中找到,时间复杂度 \(n^2\),反正还是过不去(

附:\(n^2\) 的代码:

const int MAXN=1000;
const int MOD=998244353;
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int n,m,dp[MAXN+5][MAXN+5];
int main(){
// freopen("toad.in","r",stdin);
// freopen("toad.out","w",stdout);
scanf("%d%d",&n,&m);
dp[0][n]=1;
for(int i=1;i<=n;i++) dp[i][n]=(1ll*i*qpow(i+m,MOD-2)%MOD*dp[i-1][n]+1)%MOD;
for(int j=n-1;~j;j--){
int cs=0,ct=0;
for(int i=n-j;i<=n;i++){
int coef1=1ll*m*qpow(i+m,MOD-2)%MOD;
int coef2=1ll*(i+j-n+MOD)*qpow(i+m,MOD-2)%MOD;
int coef3=1ll*(n-j)*qpow(i+m,MOD-2)%MOD;
cs=1ll*cs*coef2%MOD;ct=1ll*(ct+1)*coef2%MOD;
ct=(ct+1ll*(dp[i-1][j+1]+1)*coef3)%MOD;
cs=(cs+coef1)%MOD;ct=(ct+coef1)%MOD;
} dp[n][j]=1ll*ct*qpow((1-cs+MOD)%MOD,MOD-2)%MOD;//dp[n][j]=cs*dp[n][j]+ct
for(int i=n-j;i<n;i++){
int coef1=1ll*m*qpow(i+m,MOD-2)%MOD;
int coef2=1ll*(i+j-n+MOD)*qpow(i+m,MOD-2)%MOD;
int coef3=1ll*(n-j)*qpow(i+m,MOD-2)%MOD;
dp[i][j]=(1ll*(dp[i-1][j]+1)*coef2+1ll*(dp[i-1][j+1]+1)*coef3%MOD+1ll*(dp[n][j]+1)*coef1)%MOD;
// printf("%d %d %d\n",i,j,dp[i][j]);
} //printf("%d %d %d\n",n,j,dp[n][j]);
} printf("%d\n",dp[n][0]);
return 0;
}

接下来考虑进一步优化。这里就要一些观察了,打个表可以发现,对于 \(j\) 相同的 \(dp_{i,j}\) 而言随着 \(i\) 的增大 \(dp_{i,j}\)​ 成等差数列,换句话说所有 \(dp_{i,j}\) 都可以写成 \(k_ji+b_j\) 的形式。证明不会,大概可以归纳(?)(大概就发现 \(dp_{i,n}\) 是等差数列,而 \(dp_{i,j}\) 只从 \(dp_{i,j+1}\) 推来,这就天然地形成了归纳的模型,但具体怎么归纳我也没想出来)。这样对于每一个 \(j\),我们只用确定 \(dp_{n,j}\) 和 \(dp_{n-1,j}\),所有 \(dp_{i,j}\) 都确定了,方便起见这里假设 \(dp_{i,j}=y_j(n-i)+x_j\),这样我们可以列出这样两个方程组:

\[\begin{cases}
x_j=dp_{n-1,j+1}·\dfrac{n-j}{n+m}+(x_j+y_j)·\dfrac{j}{n+m}+x_j·\dfrac{m}{n+m}+1\\
x_j+y_j=dp_{n-2,j+1}·\dfrac{n-j}{n+m-1}+(x_j+2y_j)·\dfrac{j-1}{n+m-1}+x_j·\dfrac{m}{n+m-1}+1
\end{cases}
\]

把 \(x_j,y_j\) 解出来即可。

时间复杂度 \(n\log n\),好像做不到 \(\mathcal O(n)\)

const int MAXN=2e6;
const int MOD=998244353;
int qpow(int x,int e){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
int n,m,x[MAXN+5],y[MAXN+5],f[MAXN+5];
int calc(int i,int j){return (x[j]+1ll*(n-i)*y[j]%MOD)%MOD;}
int main(){
scanf("%d%d",&n,&m);int ivn=qpow(n+m,MOD-2),ivn1=qpow(n-1+m,MOD-2);
f[0]=1;for(int i=1;i<=n;i++) f[i]=(1ll*i*qpow(i+m,MOD-2)%MOD*f[i-1]+1)%MOD;
x[n]=f[n];y[n]=(f[n-1]-f[n]+MOD)%MOD;
for(int j=n-1;j;j--){
int a1=1ll*(n-j)*ivn%MOD;
int b1=(MOD-1ll*j*ivn%MOD)%MOD;
int c1=1ll*(n-j)*ivn%MOD*calc(n-1,j+1)%MOD;
int a2=1ll*(n-j)*ivn1%MOD;
int b2=(1-1ll*(j-1+MOD)*2*ivn1%MOD+MOD)%MOD;
int c2=1ll*(n-j)*ivn1%MOD*calc(n-2,j+1)%MOD;
(c1+=1)%=MOD;(c2+=1)%=MOD;
y[j]=1ll*(1ll*c1*a2%MOD-1ll*c2*a1%MOD+MOD)*qpow((1ll*b1*a2%MOD-1ll*b2*a1%MOD+MOD)%MOD,MOD-2)%MOD;
x[j]=1ll*(c1-1ll*y[j]*b1%MOD+MOD)*qpow(a1,MOD-2)%MOD;
} printf("%d\n",1ll*(1ll*calc(n-1,1)*n%MOD*ivn%MOD+1ll*m*ivn%MOD)*qpow(n,MOD-2)%MOD*(n+m)%MOD+1);
return 0;
}

Codeforces 1392H - ZS Shuffles Cards(DP+打表找规律)的更多相关文章

  1. HDU 4588 Count The Carries 数位DP || 打表找规律

    2013年南京邀请赛的铜牌题...做的非常是伤心.另外有两个不太好想到的地方.. ..a 能够等于零,另外a到b的累加和比較大.大约在2^70左右. 首先说一下解题思路. 首先统计出每一位的1的个数, ...

  2. Tetrahedron(Codeforces Round #113 (Div. 2) + 打表找规律 + dp计数)

    题目链接: https://codeforces.com/contest/166/problem/E 题目: 题意: 给你一个三菱锥,初始时你在D点,然后你每次可以往相邻的顶点移动,问你第n步回到D点 ...

  3. Codeforces 193E - Fibonacci Number(打表找规律+乱搞)

    Codeforces 题目传送门 & 洛谷题目传送门 蠢蠢的我竟然第一眼想套通项公式?然鹅显然 \(5\) 在 \(\bmod 10^{13}\) 意义下并没有二次剩余--我真是活回去了... ...

  4. codeforces#1090 D. New Year and the Permutation Concatenation(打表找规律)

    题意:给出一个n,生成n的所有全排列,将他们按顺序前后拼接在一起组成一个新的序列,问有多少个长度为n的连续的子序列和为(n+1)*n/2 题解:由于只有一个输入,第一感觉就是打表找规律,虽然表打出来了 ...

  5. Codeforces Round #493 (Div. 2)D. Roman Digits 第一道打表找规律题目

    D. Roman Digits time limit per test 1 second memory limit per test 256 megabytes input standard inpu ...

  6. Codeforces Beta Round #24 D. Broken robot (打表找规律)

    题目链接: 点击我打开链接 题目大意: 给你 \(n,j\),再给出 \(m[0]\) 的坐标和\(a[0]-a[n-1]\) 的坐标. 让你输出 \(m[j]\) 的坐标,其中 \(m[i]\) 和 ...

  7. 打表找规律C - Insertion Sort Gym - 101955C

    题目链接:https://cn.vjudge.net/contest/273377#problem/C 给你 n,m,k. 这个题的意思是给你n个数,在对前m项的基础上排序的情况下,问你满足递增子序列 ...

  8. Nowcoder 练习赛 17 C 操作数 ( k次前缀和、矩阵快速幂打表找规律、组合数 )

    题目链接 题意 :  给定长度为n的数组a,定义一次操作为: 1. 算出长度为n的数组s,使得si= (a[1] + a[2] + ... + a[i]) mod 1,000,000,007: 2. ...

  9. hdu 3032 Nim or not Nim? (SG函数博弈+打表找规律)

    Nim or not Nim? Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Sub ...

随机推荐

  1. Redis分布式锁的正确实现方式[转载]

    前言 分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介 ...

  2. Selenium获取动态图片验证码

    Selenium获取动态图片验证码 关于图片验证码的文章,我想大家都有一定的了解了. 在我们做UI自动化的时候,经常会遇到图片验证码的问题. 当开发不给咱们提供万能验证码,或者测试第三方网站比如知乎的 ...

  3. 【UE4 设计模式】命令模式 Command Pattern

    概述 描述 将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化:对请求排队或者记录请求日志,以及支持可撤销的操作. 命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务 ...

  4. 【c++ Prime 学习笔记】第8章 IO库

    C++语言不直接处理输入输出,而是通过标准库中的一组类来处理IO 1.2节介绍的IO库: istream(输入流)类型,提供输入 ostream(输出流)类型,提供输出 cin,是istream对象, ...

  5. CentOS 文件管理

    目录 目录管理 目录结构 切换目录 查看目录 创建目录 复制目录 剪切目录 删除目录 文件管理 查看文件 创建文件 复制文件 剪切文件 删除文件 创建链接 目录管理 目录也是一种文件. 蓝色目录,绿色 ...

  6. [技术博客]使用pylint实现django项目的代码风格检查

    使用pylint实现django项目的代码风格检查 前言 ​ 一个项目大多都是由一个团队来完成,如果没有统一的代码规范,那么每个人的代码的风格必定会有很大的差别.且不说会存在多个人同时开发同一模块的情 ...

  7. [对对子队]会议记录4.10(Scrum Meeting 1)

    本次每日例会的开会时间是4月10日晚上20:00,使用腾讯会议作为开会工具. 今天已完成的工作 何瑞 ​ 工作内容:制作UI界面的指令编辑系统,已大致实现指令的衔接 ​ 相关issue:实现用户指令编 ...

  8. [软工顶级理解组] Alpha阶段项目展示

    目录 团队成员 软件介绍 项目简介 预期典型用户 功能描述 预期目标用户数 用户反馈 团队管理 分工协作 项目管理 取舍平衡 代码管理 程序测试 代码规范 文档撰写 继续开发指导性 用户沟通 需求分析 ...

  9. oo第四次博客-UML暨学期总结

    一. 本单元两次作业架构设计 这两次作业实际上难度不大,不存在算法上的难题,大部分时间都是用在处理UML图中各个元素的关系上. 第一次UML主要处理UML类图.有UMLclass,UMLinterfa ...

  10. 嵌入式单片机stm32之DMA实验

    一. 对于大容量的STM32芯片有2个DMA控制器,控制器1有7个通道,控制器2有5个通道 每个通道都可以配置一些外设的地址. 二. 通道的配置过程: 1. 首先设置CPARx寄存器和CMARx寄存器 ...