• luogu P1409 骰子 题解


    想了一个更一般性的解法

    传送门


    【分析】

    不难得出方程,令 (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))

  • 相关阅读:
    ios app 开发中ipa重新签名步骤介绍-备
    推荐IOS开发3个工具:Homebrew、TestFight、Crashlytics-b
    iOS开发工具——统计Crash的工具Crashlytics-备用
    摘要算法
    Xcode中将图片放入Images.xcassets和直接拖入的区别
    PHP面向对象的基本写法(区别于java)
    PHP 解决时差8小时的问题
    Java-Hirbernate小结大纲
    PHP替换数据库的换行符
    Java-strurs总结
  • 原文地址:https://www.cnblogs.com/JustinRochester/p/15421704.html
Copyright © 2020-2023  润新知