有一个数x,x初始为1,等概率地执行以下两种操作:
1. 将x加1
2. 将x乘2
求x>=n(n<=1e18)时的期望操作次数,
答案(有理分数)对998244353取模
dls&jiangly做法
题解1. 矩阵快速幂的比较难想,没遇到过矩阵快速幂维护x/2的,感觉也是一个典
题解2. 有生成函数的比较难推,即使推出来之后也不会算,感觉又是一个典
以及,这个题知道做法了之后,还是很难写出与之对应的代码,很难和神仙代码对应起来…
题解1(期望dp+矩阵快速幂)
维护这么一个矩阵,考虑转移矩阵怎么写,
实际大概2的60次方到1e18,维护一个60*60的矩阵,
为了说明与ctz(二进制尾0的个数/最低位1的位置)的关系,举俩小点的例子
你可能会说,f(2)的时候,0.25f(0)+0.25f(0)和0.5f(0)不是一样的么...
但是,n稍微大一点的时候,n-1的第二行和第三行就不一样了,这种做法具有普适性
注意f(3)的时候转移矩阵只需要用第一行,而f(2)的时候需要用前两行
需要用几行,即n和n-1相比,除以2的k次方什么时候不变,
这只和ctz有关,即n的二进制尾0的个数/最低位1的位置
换言之,转移矩阵只有60种,
矩阵快速幂,以倍增的思想,求出处理出前n-1个转移矩阵积,
再乘上原始f[0]的向量,得到的向量的第一行即为答案
待补
直接复制的dls的代码,自己写不出来这么简洁,重在理解吧只能说…
#include
using namespace std;
#define rep(i,a,n) for (int i=a;i=a;i--)
#define pb push_back
#define eb emplace_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef vector VI;
typedef basic_string BI;
typedef long long ll;
typedef pair PII;
typedef double db;
mt19937 mrand(random_device{}());
const ll mod=998244353;
int rnd(int x) { return mrand() % x;}
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
// head
ll inv2=(mod+1)/2;
const int M=61;
typedef ll matrix[M+1][M+1];
matrix f[66],g[66],h[66],tmp1;
ll b[66],tmp2[66];
void mul(matrix a,matrix b,matrix c) {
rep(i,0,M+1) rep(j,0,M+1) {
c[i][j]=0;
rep(k,0,M+1) c[i][j]=(c[i][j]+a[i][k]*b[k][j])%mod;
}
}
void mulx(matrix a,ll *b,ll *c) {
rep(i,0,M+1) {
c[i]=0;
rep(j,0,M+1) c[i]=(c[i]+a[i][j]*b[j])%mod;
}
}
int main() {
for (int i=0;i=0;j--) {
f[i][j][M]=1;
rep(k,0,M+1) f[i][j][k]=(f[i][j][k]+f[i][j+1][k]*inv2)%mod;
f[i][j][j]=(f[i][j][j]+inv2)%mod;
}
}
rep(i,0,M+1) rep(j,0,M+1) {
g[0][i][j]=f[0][i][j];
h[0][i][j]=f[0][i][j];
}
rep(i,1,M) {
mul(f[i],g[i-1],h[i]);
mul(g[i-1],h[i],g[i]);
}
int _;
for (scanf("%d",&_);_;_--) {
ll n;
scanf("%lld",&n);
--n;
rep(i,0,M+1) b[i]=(i==M)?1:0;
per(i,0,M) if (n&(1ll<
待补