【题目泛做】宝藏(期望)(类欧几里得)

其实是CF868G的加强版

把概率变成任意了(好吧也没加强什么东西)做法还是一样。

注意,按理说应该要特判 p = 1 p=1 p=1的情况,然而没有卡。。。

题解:

首先确定策略,我们的策略显然是每次贪心找到当前已经被访问次数最少的洞穴。

那么每次就是访问 ( i − 1 ) ∗ k + 1 → i ∗ k (i-1)*k+1\rightarrow i*k (i1)k+1ik % n \%n %n意义下的所有洞穴。

e i e_i ei表示宝藏在 i i i号洞穴时的期望探测步数,则答案为 ∑ e i n \frac{\sum e_i}{n} nei

那么 p = 1 p=1 p=1的时候的答案为(设 t = ⌊ n − 1 k ⌋ t=\lfloor\frac{n-1}{k}\rfloor t=kn1):
∑ i = 1 n ⌊ i − 1 k + 1 ⌋ = n + k ⋅ t ⋅ ( t − 1 ) / 2 + ( n − t ⋅ k ) ⋅ t \sum_{i=1}^{n}\lfloor\frac{i-1}{k}+1\rfloor=n+k\cdot t\cdot (t-1)/2+(n-t\cdot k)\cdot t i=1nki1+1=n+kt(t1)/2+(ntk)t

然后乘上 n n n的逆元就行了

其实 e i e_i ei有表达式,我们考虑令前 j − 1 j-1 j1次访问这个山洞都没有找到宝藏,且在第 j j j次找到:

e i = ∑ j = 1 ∞ ( 1 − p ) j − 1 p ⌊ i − 1 + n ⋅ ( j − 1 ) k + 1 ⌋ e_i=\sum_{j=1}^\infty(1-p)^{j-1}p\lfloor\frac{i-1+n\cdot (j-1)}{k}+1\rfloor ei=j=1(1p)j1pki1+n(j1)+1

这个并不能用类欧几里得做,因为上标是 ∞ \infty

直接对于 i = 1 , ⋯   , n i=1,\cdots ,n i=1,,n求和也很麻烦。

但是注意到这给了我们 e i e_i ei之间的递推关系。

对于 k < i ≤ n k< i\leq n k<in e i = e i − k + 1 e_i=e_{i-k}+1 ei=eik+1

对于 1 ≤ i ≤ k 1\leq i\leq k 1ik e i = ( 1 − p ) e i − k + n + 1 e_i=(1-p)e_{i-k+n}+1 ei=(1p)eik+n+1

我们发现, % g c d ( n , k ) \%gcd(n,k) %gcd(n,k)后相同的位置形成了一个环,并且每个环上的方程是一样的,也就是说,我们把 n , k n,k n,k同时除掉gcd答案不会改变。

于是我们可以利用一次函数复合得到关于 e 1 e_1 e1的方程,然后顺着解下去,复杂度 O ( n ) O(n) O(n)

考虑一个更加优秀的做法,我们先把 e i e_i ei变成关于 i i i的函数 f ( i ) f(i) f(i)(为了写起来方便)。

显然 f f f i = 1 , 2 ⋯   , n i=1,2\cdots ,n i=1,2,n的取值形成的数列就是 e i e_i ei

现在给定 n , k n,k n,k,并且在 i = 1 , 2 , ⋯ n i=1,2,\cdots n i=1,2,n中,以 k k k为分界线函数值有一次函数复合关系。

则考虑构造一次函数 A ( x ) , B ( x ) A(x),B(x) A(x),B(x),使得 f ( i ) = B ( f ( i − k + n ) ) = A ( f ( i − k ) ) f(i)=B(f(i-k+n))=A(f(i-k)) f(i)=B(f(ik+n))=A(f(ik))

然后考虑迭代,显然我们需要对于前后分开考虑,设置一次函数 S 1 , S 2 S1,S2 S1,S2,令我们当前需要求的是:

∑ i = 1 k S 1 ( f ( i ) ) + ∑ i = k + 1 n S 2 ( f ( i ) ) \sum_{i=1}^kS1(f(i))+\sum_{i=k+1}^nS2(f(i)) i=1kS1(f(i))+i=k+1nS2(f(i))

考虑迭代,设 n ′ = k , k ′ = n % k n'=k,k'=n\%k n=k,k=n%k,那么我们需要考虑构造 A ′ , B ′ , S 1 ′ , S 2 ′ A',B',S1',S2' A,B,S1,S2使得迭代出来的形式和上面相同,及需要找到合法的这四个一次函数,使得:

∑ i = 1 k S 1 ( f ( i ) ) + ∑ i = k + 1 n S 2 ( f ( i ) ) = ∑ i = 1 k ′ S 1 ′ ( f ( i ) ) + ∑ i = k ′ + 1 n ′ S 2 ′ ( f ( i ) ) f ( i ) = B ′ ( f ( i − k ′ + n ′ ) ) = A ′ ( f ( i − k ′ ) ) \sum_{i=1}^kS1(f(i))+\sum_{i=k+1}^nS2(f(i))=\sum_{i=1}^{k'}S1'(f(i))+\sum_{i=k'+1}^{n'}S2'(f(i))\\ f(i)=B'(f(i-k'+n'))=A'(f(i-k')) i=1kS1(f(i))+i=k+1nS2(f(i))=i=1kS1(f(i))+i=k+1nS2(f(i))f(i)=B(f(ik+n))=A(f(ik))

接下来声明两个个记号:

对于函数 F F F,以下如无特殊说明 F − 1 F^{-1} F1表示 F F F的复合逆,即 F ( F − 1 ( x ) ) = F − 1 ( F ( x ) ) = I ( x ) F(F^{-1}(x))=F^{-1}(F(x))=I(x) F(F1(x))=F1(F(x))=I(x),其中 I I I表示复合操作的单位元,即 I ( x ) = x I(x)=x I(x)=x

对于函数 A A A,令 A 0 = I , A 1 = A , A i = A ( A i − 1 ) A^0=I,A^1=A,A^i=A(A^{i-1}) A0=I,A1=A,Ai=A(Ai1),即自我复合 i i i次。

很显然地,我们可以通过辗转相除直接得到 A ′ A' A B ′ B' B的表达式:
A ′ = A − ⌊ n k − 1 ⌋ ( B − 1 ) B ′ = A − ⌊ n k ⌋ ( B − 1 ) A'=A^{-\lfloor\frac{n}{k}-1\rfloor}(B^{-1})\\ B'=A^{-\lfloor\frac{n}{k}\rfloor}(B^{-1}) A=Akn1(B1)B=Akn(B1)

同样地,考虑 i = n ′ + 1 , n ′ + 2 ⋯   , n i=n'+1,n'+2\cdots,n i=n+1,n+2,n最终落在 [ 1 , n ′ ] [1,n'] [1,n]中的哪个位置上,我们同样可以得到 S 1 ′ = S 1 + ∑ i = 1 ⌊ n k ⌋ S 2 ( A i ) S 2 ′ = S 1 + ∑ i = 1 ⌊ n k − 1 ⌋ S 2 ( A i ) S1'=S1+\sum_{i=1}^{\lfloor\frac{n}{k}\rfloor}S2(A^i)\\ S2'=S1+\sum_{i=1}^{\lfloor\frac{n}{k}-1\rfloor}S2(A^i) S1=S1+i=1knS2(Ai)S2=S1+i=1kn1S2(Ai)

发现逆元,复合的 i i i次幂都很好计算。

但是这个东西相当扯淡: ∑ i = 1 t S 2 ( A i ) \sum_{i=1}^tS2(A^i) i=1tS2(Ai)

直接计算是没有办法搞的,但是我们可以算这个东西:
S 2 ( ∑ i = 1 t A i ) S2(\sum_{i=1}^tA^i) S2(i=1tAi)

但是实际上这俩不是同一个东西,我们虽然明确了这里用到的所有函数,除了 f f f全部都是一次函数,但是我们仔细考虑一下,上面两个东西在算 S 2 S2 S2的常数项被算了几次的时候就出事了,下面那个只算了一次,上面那个算了 t t t次,但是其他地方都work得很好。

所以对于所有的 S S S,我们需要把常数项单独提取出来算,才能保证答案正确。

递归边界 k = 0 k=0 k=0,这时候 f ( i ) = A ( f ( i ) ) , f(i)=A(f(i)), f(i)=A(f(i))大力计算即可。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

using std::cerr;
using std::cout;

cs int mod=1e9+7;
inline int add(int a,int b){a+=b-mod;return a+(a>>31&mod);}
inline int dec(int a,int b){a-=b;return a+(a>>31&mod);}
inline int mul(int a,int b){ll r=(ll)a*b;return r>=mod?r%mod:r;}
inline int power(int a,int b,int res=1){
	for(;b;b>>=1,a=mul(a,a))(b&1)&&(res=mul(res,a));
	return res;
}
inline void Inc(int &a,int b){a+=b-mod;a+=a>>31&mod;}
inline void Dec(int &a,int b){a-=b;a+=a>>31&mod;}
inline void Mul(int &a,int b){a=mul(a,b);}
inline void ex_gcd(int a,int b,int &x,int &y){
	if(!b){x=1,y=0;return ;}ex_gcd(b,a%b,y,x);y-=a/b*x;
}
inline int inv(int a){
	assert(a);int x,y;
	ex_gcd(a,mod,x,y);
	return (x%mod+mod)%mod;
}

struct func{
	int k,b;
	func(){}
	func(int _k,int _b):k(_k),b(_b){}
	friend func operator+(cs func &a,cs func &b){return func(add(a.k,b.k),add(a.b,b.b));}
	friend func operator-(cs func &a,cs func &b){return func(dec(a.k,b.k),dec(a.b,b.b));}
	friend func operator*(cs func &a,int b){return func(mul(a.k,b),mul(a.b,b));}
	inline int operator()(int x)cs{return add(mul(x,k),b);}
	inline func operator()(cs func &r)cs{return func(mul(k,r.k),add(mul(k,r.b),b));}
};

inline func inv(cs func &a){
	assert(a.k);int iv=inv(a.k);
	return func(iv,mul(dec(0,a.b),iv));
}

inline func pow_comp(func a,int b){
	func res(1,0);
	for(;b;b>>=1,a=a(a))if(b&1)res=res(a);
	return res;
}

inline func pow_sum(cs func &a,int n){
	int k=a.k,b=a.b;if(k==1)return func(n,mul((ll)n*(n+1)/2%mod,b));
	int iv=inv(dec(1,k)),k1=mul(mul(k,iv),dec(1,power(k,n)));
	return func(k1,mul(mul(b,iv),dec(n,k1)));
}

inline int calc(int n,int k,cs func &a,cs func &b,int s1,int s2){
	if(!k)return mul(mul(n,mul(a.b,s2)),inv(dec(1,a.k)));
	int p=n/k,t=n%k;
	func S2=pow_sum(a,p-1)*s2;Inc(S2.k,s1);
	func S1=S2+pow_comp(a,p)*s2;
	func ia=inv(a),ib=inv(b);
	func A=pow_comp(ia,p-1)(ib),B=ia(A);
	int tp=add(mul(S1.b,t),mul(S2.b,k-t));
	return add(tp,calc(k,t,A,B,S1.k,S2.k));
}

signed main(){
#ifdef zxyoi
	freopen("treasure.in","r",stdin);
#endif
	int T;scanf("%d",&T);
	while(T--){
		int n,k,p;scanf("%d%d%d",&n,&k,&p);
		if(p==1){
			int ans=n--;int t=n/k;
			Inc(ans,mul(k,(ll)t*(t-1)/2%mod));
			Inc(ans,mul(n-t*k+1,t));
			cout<<mul(ans,inv(n+1))<<"\n";
		}
		else cout<<mul(inv(n),calc(n,k,func(1,1),func(dec(1,p),1),1,1))<<"\n";
	} 
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值