loj #2325. 「清华集训 2017」小Y和恐怖的奴隶主
#2325. 「清华集训 2017」小Y和恐怖的奴隶主
题目描述
"A fight? Count me in!" 要打架了,算我一个。
"Everyone, get in here!" 所有人,都过来!
小Y是一个喜欢玩游戏的OIer。一天,她正在玩一款游戏,要打一个Boss。
虽然这个Boss有 1010010^{100}10100 点生命值,但它只带了一个随从——一个只有 mmm 点生命值的“恐怖的奴隶主”。
这个“恐怖的奴隶主”有一个特殊的技能:每当它被扣减生命值但没有死亡(死亡即生命值 ≤0\leq 0≤0),且Boss的随从数量小于上限 kkk,便会召唤一个新的具有 mmm 点生命值的“恐怖的奴隶主”。
现在小Y可以进行 nnn 次攻击,每次攻击时,会从Boss以及Boss的所有随从中的等概率随机选择一个,并扣减 111 点生命值,她想知道进行 nnn 次攻击后扣减Boss的生命值点数的期望。为了避免精度误差,你的答案需要对 998244353998244353998244353 取模。
输入格式
输入第一行包含三个正整数 T,m,kT, m, kT,m,k,TTT 表示询问组数,m,km, km,k 的含义见题目描述。
接下来 TTT 行,每行包含一个正整数 nnn,表示询问进行 nnn 次攻击后扣减Boss的生命值点数的期望。
输出格式
输出共 TTT 行,对于每个询问输出一行一个非负整数,表示该询问的答案对 998244353998244353998244353 取模的结果。
可以证明,所求期望一定是一个有理数,设其为 p/qp / qp/q(gcd(p,q)=1\mathrm{gcd}(p,q) = 1gcd(p,q)=1),那么你输出的数 xxx 要满足 p≡qx(mod998244353)。
样例
输入
3 2 6
1
2
3
输出
499122177
415935148
471393168
样例1解释
对于第一次询问,第一次攻击有 12\frac{1}{2}21 的概率扣减Boss的生命值,有 12\frac{1}{2}21 的概率扣减随从的生命值,所以答案为 12\frac{1}{2}21。1≡2∗499122177(mod998244353)。
对于第二次询问,如果第一次攻击扣减了Boss的生命值,那么有 12\frac{1}{2}21 的概率第二次攻击仍扣减Boss的生命值,有 12\frac{1}{2}21 的概率第二次攻击扣减随从的生命值;如果第一次攻击扣减了随从的生命值,那么现在又新召唤了一个随从(“恐怖的奴隶主”),于是有 13\frac{1}{3}31 的概率第二次攻击扣减Boss的生命值,有 23\frac{2}{3}32 的概率第二次攻击扣减随从的生命值。所以答案为 12∗12∗2+12∗12∗1+12∗13∗1+12∗23∗0=1112\frac{1}{2}*\frac{1}{2}*2+\frac{1}{2}*\frac{1}{2}*1+\frac{1}{2}*\frac{1}{3}*1+\frac{1}{2}*\frac{2}{3}*0 = \frac{11}{12}21∗21∗2+21∗21∗1+21∗31∗1+21∗32∗0=1211。11≡12∗415935148(mod998244353)。
样例二
见附加文件下的 ex_2.in 与 ex_2.ans。
该组样例的数据范围(除询问组数 TTT 外)同第7、8个测试点。
数据范围与提示
在所有测试点中,1≤T≤1000,1≤n≤1018,1≤m≤3,1≤k≤81 \leq T \leq 1000, 1 \leq n \leq {10}^{18}, 1 \leq m \leq 3, 1 \leq k \leq 81≤T≤1000,1≤n≤1018,1≤m≤3,1≤k≤8。
各个测试点的分值和数据范围如下:
| 测试点编号 | 分值 | T= T=T= | n≤ n\leqn≤ | m= m=m= | k= k=k= |
|---|---|---|---|---|---|
| 1 | 3 | 10 | 10 | 1 | 1 |
| 2 | 8 | 2 | 8 | ||
| 3 | 7 | 1018 {10}^{18}1018 | 3 | ||
| 4 | 12 | 7 | |||
| 5 | 30 | 20 | 3 | 5 | |
| 6 | 10 | 500 | 6 | ||
| 7 | 10 | 200 | 7 | ||
| 8 | 5 | 1000 | |||
| 9 | 10 | 100 | 8 | ||
| 10 | 5 | 500 |
/*
f[i][a][b][c]代表剩下多少次攻击次数(abc的意义同前面)的情况下期望打几次BOSS。
举个例子,如果你剩下0步,那么显然你一次都打不了。答案就是f[n][][][],比如说m=3就是f[n][0][0][1],m=2就是f[n][0][1][0];
那么f[i][a][b][c]由f[i-1][a'][b'][c']转移其中状态abc能推到状态a'b'c'。(比如说abc=4 1 2,a'b'c'=5 0 3)
以上几句话是这道题的核心,期望dp倒着转移在此题得到很深刻的体现
设计转移矩阵p[i][j]代表状态j能推到状态i的概率
但是有一种转移f[i][a][b][c]=(f[i-1][a][b][c]+1)*(1/(a+b+c+1))无法处理,很简单,我们只需要在转移矩阵的最后一列多开一行,这行的第j列表示的是第j个状态的1/(a[j]+b[j]+c[j]+1),那么在答案矩阵的最后多开一列,这一列的第一个数为1,那么在转移的时候就会把1/(a+b+c+1)多算一次
我们先预处理矩阵的 2^t 次方, 然后询问时相当于一堆矩阵乘一个向 量. 由于矩阵乘法具有结合性, 我们先将最右边的矩阵乘上向量, 得 到的还是一个向量, 继续这一过程即可.
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#define mod 998244353
using namespace std;
int p[][],dp[][][];
int x[],y[],z[],len;
int T,m,K,inv[];
long long n;
struct node{
int c[][];
inline friend node operator * (const node a,const node b){
node res;
for(int i=;i<=len;i++)
for(int j=;j<=len;j++){
long long now=;
for(int k=;k<=len;k++){
now+=1LL*a.c[i][k]*b.c[k][j];
if (now>=(long long)8e18)now%=mod;
}
res.c[i][j]=now%mod;
}
return res;
}
inline friend node operator + (const node a,const node b){
node res;
for(int i=;i<=;i++)
for(int j=;j<=len;j++){
long long now=;
for(int k=;k<=len;k++){
now+=1LL*a.c[i][k]*b.c[k][j];
if (now>=(long long)8e18)now%=mod;
}
res.c[i][j]=now%mod;
}
return res;
}
}f[],g;
inline int Pow(int a,int b){
int res=;
while(b){
if(b&)res=(1LL*res*a)%mod;
a=(1LL*a*a)%mod;
b>>=;
}
return res;
}
int main(){
freopen("Cola.txt","r",stdin);
scanf("%d%d%d",&T,&m,&K);
for(int i=;i<=;i++)inv[i]=Pow(i,mod-);//求逆元
for(int i=;i<=K;i++)
for(int j=;j<=K-i;j++)
for(int k=;k<=K-i-j;k++){//分别枚举三种生命值的随从个数,统计所有状态
if(m==&&(j||k))continue;
if(m==&&k)continue;
len++;x[len]=i;y[len]=j;z[len]=k;
dp[i][j][k]=len;
}
len++;
for(int i=;i<=len;i++)//准备矩阵p[][]
p[len][i]=inv[x[i]+y[i]+z[i]+];
for(int i=;i<len;i++)
for(int j=;j<len;j++)
if(i==j)p[i][i]=inv[x[i]+y[i]+z[i]+];
else{
if(z[i]){//打生命值为3的随从
int xx=x[i],yy=y[i]+,zz=z[i]-;
if(xx+yy+zz<K){
if(m==)xx++;
if(m==)yy++;
if(m==)zz++;
}
if(xx==x[j]&&yy==y[j]&&zz==z[j])p[j][i]=1LL*inv[x[i]+y[i]+z[i]+]*z[i]%mod;
}
if(y[i]){//打生命值为2的随从
int xx=x[i]+,yy=y[i]-,zz=z[i];
if(xx+yy+zz<K){
if(m==)xx++;
if(m==)yy++;
if(m==)zz++;
}
if(xx==x[j]&&yy==y[j]&&zz==z[j])p[j][i]=1LL*inv[x[i]+y[i]+z[i]+]*y[i]%mod;
}
if(x[i]){//打生命值为1的随从
int xx=x[i]-,yy=y[i],zz=z[i];
if(xx==x[j]&&yy==y[j]&&zz==z[j])p[j][i]=1LL*inv[x[i]+y[i]+z[i]+]*x[i]%mod;
}
}
for(int i=;i<=len;i++)
for(int j=;j<=len;j++)
f[].c[i][j]=p[i][j];
for(int i=;i<=;i++)f[i]=f[i-]*f[i-];
while(T--){
cin>>n;
memset(g.c,,sizeof(g.c));
g.c[][len]=;
for(int i=;i>=;i--)//一个向量与若干个矩阵相乘
if((1LL<<i)<=n){
n-=1LL<<i;
g=g+f[i];
}
printf("%d\n",g.c[][dp[m==][m==][m==]]);
}
return ;
}
loj #2325. 「清华集训 2017」小Y和恐怖的奴隶主的更多相关文章
- LibreOJ #2325. 「清华集训 2017」小Y和恐怖的奴隶主(矩阵快速幂优化DP)
哇这题剧毒,卡了好久常数才过T_T 设$f(i,s)$为到第$i$轮攻击,怪物状态为$s$时对boss的期望伤害,$sum$为状态$s$所表示的怪物个数,得到朴素的DP方程$f(i,s)=\sum \ ...
- 【loj2325】「清华集训 2017」小Y和恐怖的奴隶主 概率dp+倍增+矩阵乘法
题目描述 你有一个m点生命值的奴隶主,奴隶主受伤未死且当前随从数目不超过k则再召唤一个m点生命值的奴隶主. T次询问,每次询问如果如果对面下出一个n点攻击力的克苏恩,你的英雄期望会受到到多少伤害. 输 ...
- LOJ2325. 「清华集训 2017」小 Y 和恐怖的奴隶主【矩阵快速幂优化DP】【倍增优化】
LINK 思路 首先是考虑怎么设计dp的状态 发现奴隶主的顺序没有影响,只有生命和个数有影响,所以就可以把每个生命值的奴隶主有多少压缩成状态就可以了 然后发现无论是什么时候一个状态到另一个状态的转移都 ...
- LOJ2325「清华集训 2017」小Y和恐怖的奴隶主
题目链接 首先dp很显然,\(f(i,s)\)表示到了第i轮,各种血量人数的情况为s今后的期望攻击boss次数.那么有\(f(i,s)=\frac{1}{num+1}*\sum_{s->s'}( ...
- Loj #2324. 「清华集训 2017」小 Y 和二叉树
Loj #2324. 「清华集训 2017」小 Y 和二叉树 小Y是一个心灵手巧的OIer,她有许多二叉树模型. 小Y的二叉树模型中,每个结点都具有一个编号,小Y把她最喜欢的一个二叉树模型挂在了墙上, ...
- [LOJ#2324]「清华集训 2017」小Y和二叉树
[LOJ#2324]「清华集训 2017」小Y和二叉树 试题描述 小Y是一个心灵手巧的OIer,她有许多二叉树模型. 小Y的二叉树模型中,每个结点都具有一个编号,小Y把她最喜欢的一个二叉树模型挂在了墙 ...
- [LOJ#2323]「清华集训 2017」小Y和地铁
[LOJ#2323]「清华集训 2017」小Y和地铁 试题描述 小Y是一个爱好旅行的OIer.一天,她来到了一个新的城市.由于不熟悉那里的交通系统,她选择了坐地铁. 她发现每条地铁线路可以看成平面上的 ...
- 【UOJ#340】【清华集训2017】小 Y 和恐怖的奴隶主(矩阵快速幂,动态规划)
[UOJ#340][清华集训2017]小 Y 和恐怖的奴隶主(矩阵快速幂,动态规划) 题面 UOJ 洛谷 题解 考虑如何暴力\(dp\). 设\(f[i][a][b][c]\)表示当前到了第\(i\) ...
- loj2324 「清华集训 2017」小 Y 和二叉树
https://loj.ac/problem/2324 太智障,一开始以为中序遍历的第一个点一定是一个叶子,想了个贪心.然而,手算了一下,第一个点都过不了啊. input 5 2 3 4 1 3 3 ...
随机推荐
- invalid constant type: 18
javassist 3.18以下的版本不支持在JDK1.8下运行
- Unity3D之Mesh(六)绘制扇形、扇面、环形
前言: 绘制了圆,就想到绘制与之相关的几何图形,以便更灵活的掌握Mesh动态创建模型的机制与方法. 一.分析: 首先,结合绘制圆的过程绘制环形: 圆形是由segments个等腰三角形组成的(上一篇中, ...
- java-StringBuffer常用方法
对字符串进行修改的时候,需要使用可变长字符串StringBuffer 和 StringBuilder 类. append(String s):将指定的字符串追加到此字符序列. Reverse():将此 ...
- ffmpeg处理RTMP流媒体的命令和发送流媒体的命令(UDP,RTP,RTMP)
将文件当做直播送至live ffmpeg -re -i localFile.mp4 -c copy -f flv rtmp://server/live/streamName re限制输出速率, ...
- HNOI2008玩具装箱 斜率优化
题目描述 P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京.他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中.P教授有编号为1...N的N件玩具, ...
- Maven 将jar导入本地maven仓库
目录 环境变量配置maven 执行一下命令即可 诚邀访问我的个人博客:我在马路边 更好的阅读体验点击查看原文:Maven将jar倒入本地maven仓库 原创博客,转载请注明出处 @ 在Java项目开发 ...
- Swift中的参数内部名称和外部名称
1.什么是参数的内部名称和外部名称? struct Color { let red, green, blue: Double init(red redColor: Double,green green ...
- 白话算法(6) 散列表(Hash Table)从理论到实用(上)
处理实际问题的一般数学方法是,首先提炼出问题的本质元素,然后把它看作一个比现实无限宽广的可能性系统,这个系统中的实质关系可以通过一般化的推理来论证理解,并可归纳成一般公式,而这个一般公式适用于任何特殊 ...
- 【转】 Pro Android学习笔记(五一):ActionBar(4):标准和Tab模式
之前,我们学习的Action Bar是标准模式,Tab模式的如下图所示. 对于Tab,我们在Android学习笔记(二二): 多页显示-Tag的使用中学习过,但Action Bar的tab更适合fra ...
- 排序----demo----
排序1---冒泡法: 单向冒泡排序的基本原理就是:对于给定的n个数据,从第一个数据开始一次对相邻的两个数据进行比较,当前面的数据大于后面的数据时,交换位置,进行一轮比较和换位后,n个数据中最大的那个被 ...