想了一个更一般性的解法
【分析】
不难得出方程,令 (f_{n, m}) 表示共 (n) 个人,第 (m) 个人获胜的概率
则 (f_{n, m}=egin{cases} {1over 2}f_{n, m-1}+{1over 3}f_{n-1, m-1}&m>1 \\ {1over 2}f_{n, n}+{1over 6}&n>1wedge m=1 \\ 1&n=1 end{cases})
那么考虑 (f_{n-1, 1}sim f_{n-1,n-1}) 已经计算得出,现在要转移 (f_{n, 1}sim f_{n, n})
但由于 (f_{n, 1}) 的递推式中含有 (f_{n, n}),无法朴素递推。一般这类构成一堆等式的问题需要高斯消元求解,但总复杂度 (O(n)cdot O(n^3)=O(n^4)) 显然是不可能的
但有一个很显然的事情是,如果已知 (f_{n, n}) ,则可推出 (f_{n, 1}) ,而后递推算出 (f_{n, 2}sim f_{n, n-1})
那么这样就可以二分一个 (f_{n, n}) 然后递推回 (f_{n, n}) ,再检查是否符合。这样的话复杂度是 (O(n^2log varepsilon)) 的,但无法解决模意义下的问题。
考虑类似拓域的做法(实际上不是拓域),往实数域 (<R, +, *>) 中再引入一个域 (f_{n, n}cdot R)
看不懂的,可以理解为像复数一样,再添一维
那我们可以用二元组 (left<a, b ight>=a+bcdot f_{n, n}) 来描述这个“域”中的每一个元素
则这个域的运算,有
(egin{aligned} left<a, b ight>+c&=&left<a+c, b ight> \\left<a,b ight>cdot c&=&left<acdot c,bcdot c ight> end{aligned})
其他的不重要,就不写了
那我们可以初始化 (f_{n, 1}=left<{1over 6}, {1over 2} ight>)
然后直接 (O(n)) 递推到 (f_{n, n}=left<a,b ight>)
又有 (f_{n, n}=left<a,b ight>=a+bcdot f_{n, n})
所以解方程得 (f_{n, n}={aover 1-b})
接下来就可以求出 (f_{n, 1}) ,再 (O(n)) 递推出 (f_{n, 2}sim f_{n, n-1})
总复杂度优化为 (O(n^2))
如果题目是模意义下的,这个方法显然也是适用的
【代码】
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pii;
typedef double db;
typedef pair<db, db> pdd;
#define fi first
#define se second
inline pdd operator / (const pdd &a, db b) { return pdd(a.fi/b, a.se/b); }
inline pdd operator + (const pdd &a, db b) { return pdd(a.fi+b, a.se); }
const int MAXN=1024;
db f[MAXN][MAXN];
inline void init() {
f[1][1]=1;
for(int n=2; n<=1000; ++n) {
pdd p(1.0/6, 1.0/2);
for(int m=2; m<=n; ++m)
p=p/2+f[n-1][m-1]/3;
f[n][n]=p.fi/(1-p.se);
f[n][1]=1.0/6+f[n][n]/2;
for(int m=2; m<=n; ++m)
f[n][m]=f[n][m-1]/2+f[n-1][m-1]/3;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
init();
int n, m;
cin>>n>>m;
cout<<fixed<<setprecision(9)<<f[n][m];
cout.flush();
return 0;
}
【拓展】
对于像 (f_{n, n}) 这样要预先确定的项,我们假定为预先项
对于这类问题,当预先项数量为 (k) 时
如果对于每个预先项进行二分,则复杂度为 (O(ncdot log^kvarepsilon))
如果采用该方法,可以“拓域”的时候拓 (k) 维,这样的转移是 (O(kn)) 的,最后对预先项进行高斯消元 (O(k^3)) ,总复杂度为 (O(kn+k^3))
可以发现,当 (k) 较小时(比如 (k=1,2,3)),这个方法是很优秀的,几乎是 (O(n)) 的