概率習題集(from kuangbin)

virtual judge中的kuangbin專題.鏈接:點擊打開鏈接 .入口二:點擊打開鏈接

分類瀏覽:

簡單題:L.E.M

根據優惠劵收集原理:G.N

推導一元狀態方程:A.B.C.C(1).F.K

推導多元狀態方程:J.Q

需要特別優化:C(1)

概率dp:D.I.O

存在環只能用高斯消元:F.

剽悍的思路:H.J

狀態壓縮:K.O

P題沒能完全理解

A.

題意:n個門,每次從n個門中任選一個門,如果該門後面對應的值爲負數ai,表示經過時間|ai|,又會回到原點。如果該門後面是正數,則表示經過時間ai後走出迷宮。經過多長時間t能走出迷宮,求該時間的期望。如果不可能走出迷宮輸出inf。

解析:設當前走出該迷宮的期望爲X。

x =∑ 1.0 / n * ai(ai > 0) + ∑1.0 / n * (x + ai) (ai < 0)

對該式子進行化簡,假設正數的個數爲p,解得: x =1.0 *( 正數和 + 負數絕對值和 )/ p

代碼:另:需要判斷如果都是負數則不可能走出迷宮

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;

int main()
{
	//freopen("in.txt","r",stdin);
	int T,a;
	scanf("%d",&T);
	for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int n,p = 0;
        int sum1 = 0,sum2 = 0;
        scanf("%d",&n);
        for(int i = 0; i < n; i ++)
        {
            scanf("%d",&a);
            if(a> 0)
            {
                sum1 += a;
                p ++;
            }
            else
            {
                sum2 += -1 * a;
            }
        }
        int fenzi = sum1 + sum2;
        int fenmu = p;
        if(p == 0)
        {
            printf("inf\n");
            continue;
        }
        else
        {
            int k = __gcd(fenzi,fenmu);
            printf("%d/%d\n",fenzi/k,fenmu/k);
        }
    }
	return 0;
}

B.

題意:你在點1,要走到點n。你選擇扔骰子(六個面)的方法,投到多少走幾步,但如果投的點數將走的超過點n,則要重新投骰子。每一點都有一個價值,走到該點就能獲得,問走到終點時能獲得的價值的期望。顯然點1,點n 的價值是可以獲得的。

解析:f(x)爲從x點出發能獲得的價值的期望。邊界f(n) = an (邊界可以不用)

f(x) = 1.0 / 6 * f(x + 1) + 1.0 / 6 * f(x + 2)  + 1.0 / 6 * f(x + 3) + ……+1.0 / 6 * f(x + 6) + ax

當從該點出發可以到達超過點n時,假設可以走的方案有cnt 種

f(x) = 1.0 / 6 * f(x + 1) + ……+1.0 / 6 * f(x + cnt) + (6 - cnt) / 6.0 * f(x) + ax

整理後得到:f(x) = 1.0 / cnt * f(x + 1)+ ……+1.0 / cnt * f(x + cnt)

易知:cnt = n - x

代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
int a[105];
double  dp[105];
bool vis[105];
int n;
double dfs(int x)
{
    //if(x == n)
      //  return a[n];
    if(vis[x] == true)
        return dp[x];
    vis[x] = true;
    dp[x]  =  a[x];
    int num = 6;
    if(6 + x > n)
    {
        num = n - x;
    }
    for(int i = 1; i <= num; i ++)
    {
        dp[x] += 1.0 / num * dfs(x + i);
    }
    return dp[x];
}
int main()
{
    //freopen("in.txt","r",stdin);
    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        memset(vis,false,sizeof(vis));
        scanf("%d",&n);
        for(int i = 1; i <= n; i ++)
        {
            scanf("%d",&a[i]);
        }
        double sum = 0;
        sum = dfs(1);
        printf("%f\n",sum);
    }
    return 0;
}

C(pre 注意是另一道題).題目鏈接:http://bak2.vjudge.net/problem/20869     uva11762

題意:我們都知道,任何一個整數都可以寫成幾個質數的乘積。那麼我們將一個整數n,每次從所有小於等於n的素數中任選一個p,如果n能被該素數p整除,則n變爲n/p.如果不能,則n不變。問將n變爲1,需要選的次數的期望。

解析:f(x) 表示將x變爲1,需要選的次數的期望是多少。其中n表示小於等於x的素數有幾個

f(x) = ∑1.0 / n * f(x / i) (能被整除) + ∑ 1.0 / n * f(x)(不能被整除) + 1

整理後得到:f(x) = (∑f(x / i) + n) / k * 1.0

另:需要素數打表

此題時間複雜度較高時間爲10s

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
typedef long long LL;
double dp[1000005];
bool vis[1000005];
bool isPrime[2000005];
LL primeList[1000005],primeCount = 0;
void primeInit(LL n)
{

    memset(isPrime,true,sizeof(isPrime));//??????????
    int m = sqrt(n + 0.5);
    for(int i = 2; i <= m;  i ++)
    {
        if(isPrime[i])//?????
        {
            for(int j = i * i; j <= n; j += i)
            {

                isPrime[j] = false;
            }
        }
    }
    for(int i = 2; i <= n; i ++)
    {
        if(isPrime[i])
        {

            primeList[primeCount] = i;
            primeCount ++;
        }

    }
}
double dfs(int a)
{
    if(a == 1)
        return 0.0;
    if(vis[a] ==  true)
        return dp[a];
    double ans = 0.0;
    vis[a] = true;
    int p = 0,g = 0;
    while( p < primeCount)
    {
        if(primeList[p] > a)
        {
            break;
        }
        if(a % primeList[p] == 0)
        {
            ans += dfs(a / primeList[p]);
            g ++;
        }
        p ++;
    }
    if(g == 0)
    {
        return 0;
    }

    ans = (ans + p) / g;
    dp[a] = ans;
    return ans;
}
int main()
{
//freopen("in.txt","r",stdin);
    primeInit(2000000);

    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        int x;
        scanf("%d",&x);
        //減少T大時候的複雜度,對相同n結果是確定的
      //  memset(vis,false,sizeof(vis));
       // memset(dp,0,sizeof(dp));

        double ans =  dfs(x);

        printf("Case %d: %f\n",t,ans);
    }
    return 0;
}

C.(1)

題意:給定一個數N。從N的約數中任選一個i,另N = N / i問要進行這樣操作期望的次數,使得N = 1。這裏的約數從1到n

解析:先分析狀態方程。設x要到1的期望爲f(x)

f(x) = ∑1.0 / n * f(x/i) (i爲x的約數) + 1.0 / n * f(x) + 1

整理後得:f(x) = 1.0 / (n - 1) * ∑f(x / i) + n / (n - 1) * 1.0

開始時利用記憶化搜索dp來寫,但一直都TLE(3s,題目要求2s)

實際上該題還需要進一步化簡,在求x的約數時可以只求到sqrt(x),如果一個數小於sqrt(x)且是x的約數,那麼一定有一個與ta相乘爲x,且大於sqrt的約數,如果這兩個約數不想等,就把這兩個因數都得f都加入到結果中。可以直接打表求。

另:注意求n/(n - 1)時要轉化爲double型。

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
int const maxn = 100000;
double  d[maxn + 10];
void init()
{
    int cnt;
    double  sum;
    for(int i = 2; i <= maxn ; i ++)
    {
        cnt = 0;
        sum = 0;
        for(int j = 1; j * j <= i; j ++)
        {
            if(i % j == 0)
            {
                cnt ++;
                sum += d[j];
                if(j != i / j)
                {
                    cnt ++;
                    sum += d[i / j];
                }
            }

        }
        d[i] = 1.0 * (sum + cnt) / (cnt - 1);
    }

}
int main()
{
   // freopen("in.txt","r",stdin);
    int T,a;
    init();
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        scanf("%d",&a);
        printf("%f\n",d[a]);
    }
    return 0;
}

D.

題意:要求搶劫銀行被抓住的概率小於p,銀行的個數爲n,沒家銀行都有Mi的錢,在該銀行被捉住的概率爲pi。求保證被抓住概率小於p時,最多能搶到多少錢。

解析:顯然是dp。dp[x]表示搶劫x,被抓住的最小概率

dp[x] = dp[x - m[i]] + (1 - dp[x - m[i]]) * pi(在保證dp[x - m[i]] + (1 - dp[x - m[i]]) * pi < p的前提下)

外層循環從0到n - 1.內層循環錢數從最大值到最小值(保證x - m[i] 的結果是上一層循環的,而不是這一層循環所產生的,其實是二維dp的簡化。)

代碼:注意初始化dp均爲1(一定會被抓住)dp[0] = 0沒搶一定沒被抓住

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
int a[105];
double p[105];
double dp[10005];

int main()
{
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	int T;
	scanf("%d",&T);
	for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int n;
        double P;
        scanf("%lf%d",&P,&n);
        int sum = 0;
        for(int i = 0; i < n; i ++)
        {
            scanf("%d%lf",&a[i],&p[i]);
            sum += a[i];
        }
       for(int i = 0; i <  10005; i ++)
       {
           dp[i] = 1;
       }

        dp[0] = 0;
         for(int i = 0; i < n; i ++)
        {
            for(int x = sum; x >= 0; x --)
            {
                if(x - a[i] >= 0)
                {
                    if((1 - dp[x - a[i]])* p[i] + dp[x - a[i]] < P)
                    {
                        dp[x] = min(dp[x] ,(1 - dp[x - a[i]]) * p[i] + dp[x - a[i]]);
                    }

                }
            }
        }
        int ans = 0;
        for(int i = 0; i <= sum; i ++)
        {
            if(dp[i] < P)
                ans = i;
        }
        printf("%d\n",ans);
    }
	return 0;
}
E.

題意:一年有n天,問至少有多少個人,才能保證有人生日在同一天的概率大於等於0.5

解析:假設有x個人,那麼所有人生日不在一天的概率P爲

分母:n * n * n * n(x個n)

分子:n * (n - 1) *……*(n - x + 1)

有分子分母項數的個數是一樣的。對應相除即可求解。枚舉人數x使得 1 - P >= 0.5。

優化:每增加一個人實際上只是多成一項 n / (n - x + 1)

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;

int main()
{
	//freopen("in.txt","r",stdin);
	int T;
	scanf("%d",&T);
	for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int n;
        scanf("%d",&n);
        int ans = 1;
        double p = 1;
        while(1)
        {

                p = p * (n - ans + 1) / n;


            if(1.0 - p >= 0.5)
            {
                break;
            }
            ans ++;
        }
        printf("%d\n",ans - 1);

    }
	return 0;
}

F.(此題理解不夠深刻)

題意:首先有100個格子,你在第一個格子,要去第100個格子。扔骰子(6面的),投幾走幾,如果超過100就重新投。另外給出n個點 有兩個座標(a,b)如果你到達了a點就會無條件的傳輸到b點。(可以從數值大的點傳達小點,反之也是)問從1 - 100 扔骰子次數的期望。

解析:考慮在沒有傳輸的情況下。與b題類似

設f(x)爲從x點走到100點扔骰子的期望

當x不會超出邊界時:

f(x) = 1.0 / 6 * f(x + 1) + 1.0 / 6 * f(x + 2)  + 1.0 / 6 * f(x + 3) + ……+1.0 / 6 * f(x + 6) + 1

當從該點出發可以到達超過點n時,假設可以走的方案有cnt 種

f(x) = 1.0 / 6 * f(x + 1) + ……+1.0 / 6 * f(x + cnt) + (6 - cnt) / 6.0 * f(x) +1

整理後得到:f(x) = 1.0 / cnt * f(x + 1)+ ……+1.0 / cnt * f(x + cnt) + 6.0 / cnt

但是啊因爲可以來回傳輸,就一定會有環。所以只能用高斯消元來求解方程組,那麼方程組是什麼呢

對於每個點來說方程爲下面的二選一

1.f(x) = 1.0 / cnt * f(x + 1)+ ……+1.0 / cnt * f(x + cnt) + 6.0 / cnt

f(x) - 1.0 / cnt * f(x + 1)+ ……+1.0 / cnt * f(x + cnt) = 6.0 / cnt

代碼中寫的是

cnt * (x) -  f(x + 1)+ ……f(x + cnt) = 6.0

cnt = n - x.if(cnt > 6)cnt = 6

2.f(x) = f(b)

f(x) - f(b) = 0

一共有100個方程,但由於模板是從1開始的,從0 - equ - 1固傳入的數字應爲101

一共有101個變量。高斯 消元中的變元是包括常數的。模板中的變量是從0 - var

代碼:(列方程+ 套模板)

#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
#include<math.h>
using namespace std;

const int MAXN=200;





int mapp[MAXN];


const int maxn=1002;
const double eps=1e-12;
double a[maxn][maxn];

double x[maxn];//解集
bool free_x[maxn];
int n;

int sgn(double x)
{
    return (x>eps)-(x<-eps);
}
// 高斯消元法解方程組(Gauss-Jordan elimination).(0表示無解,1表示唯一解,大於1表示無窮解,並返回自由變元的個數)
int Gauss(int equ,int var)
{

    int i,j,k;
    int max_r; // 當前這列絕對值最大的行.
    int col; // 當前處理的列.
    double temp;
    int free_x_num;
    int free_index;
    // 轉換爲階梯陣.
    col=0; // 當前處理的列.
    memset(free_x,true,sizeof(free_x));
    for(k=0; k<equ&&col<var; k++,col++)
    {
        max_r=k;
        for(i=k+1; i<equ; i++)
        {
            if(sgn(fabs(a[i][col])-fabs(a[max_r][col]))>0)
                max_r=i;
        }
        if(max_r!=k)
        {
            // 與第k行交換.
            for(j=k; j<var+1; j++)
                swap(a[k][j],a[max_r][j]);
        }
        if(sgn(a[k][col])==0)
        {
            // 說明該col列第k行以下全是0了,則處理當前行的下一列.
            k--;
            continue;
        }
        for(i=k+1; i<equ; i++)
        {
            // 枚舉要刪去的行.
            if (sgn(a[i][col])!=0)
            {
                temp=a[i][col]/a[k][col];
                for(j=col; j<var+1; j++)
                {
                    a[i][j]=a[i][j]-a[k][j]*temp;
                }
            }
        }
    }

    for(i=k; i<equ; i++)
    {
        if (sgn(a[i][col])!=0)
            return 0;
    }
    if(k<var)
    {
        for(i=k-1; i>=0; i--)
        {
            free_x_num=0;
            for(j=0; j<var; j++)
            {
                if (sgn(a[i][j])!=0&&free_x[j])
                    free_x_num++,free_index=j;
            }
            if(free_x_num>1) continue;
            temp=a[i][var];
            for(j=0; j<var; j++)
            {
                if(sgn(a[i][j])!=0&&j!=free_index)
                    temp-=a[i][j]*x[j];
            }
            x[free_index]=temp/a[i][free_index];
            free_x[free_index]=0;
        }
        return var-k;
    }

    for (i=var-1; i>=0; i--)
    {
        temp=a[i][var];
        for(j=i+1; j<var; j++)
        {
            if(sgn(a[i][j])!=0)
                temp-=a[i][j]*x[j];
        }
        x[i]=temp/a[i][i];
    }
    return 1;
}
int main(void)
{
   // freopen("in.txt", "r", stdin);
    //freopen("out.txt","w",stdout);

    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        memset(mapp,0,sizeof(mapp));
        memset(a,0,sizeof(a));
        printf("Case %d: ",t);
        int n;
        scanf("%d",&n);
        int p,q;
        for(int i = 0; i < n; i ++)
        {
            scanf("%d%d",&p,&q);
            mapp[p] = q;
        }
        a[100][100] = 1,a[100][101] = 0;
        for(int i = 1; i < 100; i ++)
        {
            if(mapp[i] != 0)
            {
                a[i][i] = 1;
                a[i][mapp[i]] = -1;
                a[i][101] = 0;
            }
            else
            {
                int k = 0;
                for(int j = 1; j <= 6 && i + j <= 100; j ++)
                {
                    a[i][i + j] = -1;
                    k ++;
                }
                a[i][i] = k;
                a[i][101] = 6;
            }
        }
        Gauss(101,101);
        printf("%.16f\n",x[1]);

    }
    return 0;
}

G.

題意:假設一個骰子有n面,設扔x次能使得每面至少被扔到一次,求x的期望。

解析:可以直接觀察找規律。但注意此題是有一類經典題目優惠劵收集問題:鏈接http://www.guokr.com/article/5583/

第一次扔骰子,想要扔到一個新的點數需要扔一次,第二次扔骰子,扔到新的點數的概率爲 (n - 1)/ n.那麼需要扔骰子的期望數爲n/ (n - 1) ,再扔骰子,需要扔骰子的期望數爲n/(n - 2) ……直到最後一次扔骰子,扔到新的點數的概率爲1 / n,需要扔 n / 1次

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;

int main()
{
   // freopen("in.txt","r",stdin);
    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int n;
        scanf("%d",&n);
        double ans = 1.0;
        for(int i = 1; i < n; i ++)
        {
            ans += 1.0 * n / i;
        }
        printf("%.8f\n",ans);

    }
    return 0;
}

H.

題意:你在一個島上,島上有老虎,鹿,還有你。另外有一下幾條規則:1.如果你遇到老虎,老虎就會吃掉你。2.如果鹿遇到老虎,鹿會被老虎吃掉。3.如果鹿遇到鹿,相安無事4.如果你遇到鹿,你會殺死鹿,或者留下鹿。5.如果老虎遇到老虎,它們會同歸於盡。每天兩個生物將會相遇,如果老虎都死了,你將獲救。如果遇到鹿,你要做出最佳的選擇,求你存貨的概率是多少

解析:此題的破題點在於與鹿的個數是沒有關係的。因爲鹿的出現只會增加輪數,對老虎是否會同歸於盡沒有任何影響。

如果老虎的個數是單數,人必死。如果老虎的個數爲0,人比活。

每次從所有的老虎和人中選擇兩個,再從老虎中選擇兩個,直到沒有老虎了

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
double dp[1005][1005];

int main()
{
    // freopen("in.txt","r",stdin);
    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {

        memset(dp,0,sizeof(dp));
        printf("Case %d: ",t);
        int x,y;
        double ans = 1;
        scanf("%d%d",&x,&y);
        if(x == 0)
        {
            ans = 1;
        }
        else if(x % 2 != 0)
        {
            ans = 0;
        }
        else
        {
         //   cout<<1<<endl;
            while(x)
            {
                ans = ans*1.0 * (x - 1) * x /  x / (x + 1);
                x = x - 2;
            }
        }
        printf("%.8f\n",ans);

    }
    return 0;
}

I

題意:題面很長。意思就是有n個yes / no,它們的總長度爲s。根據這個能算出有多少個yes,多少個no。對於有確定個數的yes、no的每一種排列。比如

no yes no yes.每一個yes/no向後移一位,前面補yes

yes no yes no 這樣對於這種排列就有四個是錯的,求對於所有排列錯誤數的期望

解析:dp。dp[i][j][k],i表示前i個位置,j表示前i個位置有幾個yes,k表示當前位置是yes還是no。0表示yes,1表示no.整體表示對於這種情況錯誤的期望數。

p表示的是第i個位置之後的位置是yes的可能性。p = (yes個數 - j) / (n - i)

q爲no的可能性q = 1.0 - p

dp[i][j][0] = dp[i + 1][j + 1][0] * p + (dp[i + 1][j][1] + 1)* q;

dp[i][j][1] =  (dp[i + 1][j + 1][0] + 1) * p + dp[i + 1][j][1] * q

最後的值爲dp[1][1][0] + dp[1][0][1]分別表示第一個位置是yes,和no

由於存不下,第一位採用的是滾動數組。dp的第一維應當是從大到小的,第二 維要注意保證yes的個數是合法的,既不能少於i - no的個數,也不能大於i,yes的個數

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
double dp[2][5005][2];//yes = 0,no = 1;
int main()
{
    //freopen("in.txt","r",stdin);
    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int n,s;
        scanf("%d%d",&n,&s);
        int x = s - 2*n,y = 3 * n - s ;//x表示yes的個數,y表示no的個數
        memset(dp,0,sizeof(dp));
        int cur = 1;//滾動數組,表示i == 1 時求出的值是存在dp[0]中單 
        if(n % 2 == 0)
            cur = 0;
        //dp[cur][x][0] = dp[cur][x][1] = 0;
        for(int i = n - 1; i >= 1; i --)
        {


            // cout<<cur<<endl;
            cur = cur ^ 1;

            for(int j = max(i - y, 0 ); j <= i&& j <= x; j++)
            {
                double p1 = 1.0 * (x - j) *1.0/ (n - i);
                double p2 = 1.0 - p1;
                dp[cur][j][0] = dp[cur ^ 1][j + 1][0]  * p1 +( dp[cur ^ 1][j][1] + 1)* p2;
                dp[cur][j][1] =  (dp[cur ^ 1][j + 1][0] + 1) * p1 + dp[cur ^ 1][j][1] * p2;
            }

        }
        printf("%f\n",dp[1][1][0] * x / n +( dp[1][0][1]  + 1)* y / n);
    }
    return 0;
}

J.

題意:從長寬高分別爲xyz中,先任選三個x1,y1,z1,再任選三個x2,y2,z2將min(x1, x2) ≤ x ≤ max(x1, x2),min(y1, y2) ≤ y ≤ max(y1, y2) andmin(z1, z2) ≤ z ≤ max(z1, z2).的燈全部打開。開始爲全部關上。重複k輪。問開着燈的個數的期望。

解析:先求解出每個點的等開着的概率。設該點爲a,b,c.選擇的x爲x1,x2.該點開燈的概率爲:1.0 - x1,x2都選在該點左側的概率(a - 1) * (a - 1) / (x * x) - 都選在該點右側的概率(x - a) * (x - a) / (x * x) 。設每個點一次後被按到的概率爲p。那麼k輪後開着燈的概率爲 被按到的概率爲奇數的概率。設按k次被按到的次數爲奇數的概率爲f(k) .被按到次數爲偶數的概率爲 g(k)。f(k) = f(k - 1) * ( 1 - p) + g(k - 1) * p.g(k) = g(k - 1) * (1 - p) + f(k -1) * p.f(1) = p,g(1) = 1 - p.f(k) + g(k) = 1

整理後可以得到:f(k) = f(k - 1) * ( 1 - 2*p) + p

另f(k) + C = (1 - 2p) * (f(k - 1) + C) (解出C= -1.0 / 2.0)

就得到了:f(k) = ( 1 - ( 1 - 2p) ^k) / 2.0;

將每個點的概率求出來,再乘以總共燈的個數。

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
double fun(int x,int y)
{
    double ans = 1.0 -1.0 *(  (x - 1) * (x - 1) + (y - x) * (y - x) )/ (y * y);
    return ans;
}
int main()
{
    //freopen("in.txt","r",stdin);
    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int a,b,c,n;
        double p = 0,ans = 0;
        scanf("%d%d%d%d",&a,&b,&c,&n);
        if(n == 0)
        {
            printf("0\n");
            continue;
        }

        for(int i = 1; i <= a; i ++)
        {
            for(int j = 1; j <= b; j ++)
            {
                for(int k = 1; k <= c; k ++)
                {
                    p = fun(i,a) * fun(j,b) * fun(k,c) ;
                    ans += -0.5 * pow(1 - 2*p,1.0 * n) + 0.5;
                }
            }
        }


        printf("%.8f\n",ans);

    }
    return 0;
}

K.(沒有理解好)

題意:T組數據,n個頂點,m個邊。之後是m條邊,分別是兩個頂點,和權值及跑過路徑的時間(u,v,w).壞人一開始在接點零處,壞人經過一個頂點後就不能再次回到那個頂點了。壞人可以走到有邊和它相連,並且從該點出發可以經過所有未經過的點。假設這樣的方案有p種。壞人還可以就在該點再呆5min。這樣壞人選擇每一種方案的概率爲1/(1+p)如果沒有頂點可以走的時候,壞人被警察抓住。求壞人被警察抓住所需時間的期望

解析:狀態壓縮 + dp。將每個點是否走過,用二進制壓縮成一串零一序列。之後用dfs記憶化搜索,判斷條件爲狀態是否爲每個點都走過,如果都做過則返回true

狀態方程爲設從當前點和狀態出發一共可以走的點數爲cnt。從當前點和狀態出發被警察抓住時間的期望爲f(x)

f(x) = 1.0 / (cnt + 1) * (f(x) + 5.0) + ∑1.0 / (cnt + 1) * (wi + f(y))(從當前點可以到達的狀態和位置)

整理後:f(x) = (∑ (wi + f(y)) + 5 ) / cnt

爲什麼是狀態壓縮呢?因爲當前點是否能夠遍歷所有安全的點,與哪些點是已經遍歷過的有關。也就是說要已知過去的狀態才能推導當前的狀態。即必須要存儲當前狀態

代碼:

/*int t,n,m;
double mp[maxn][maxn];
double dp[maxn][1<<maxn];
bool vis[maxn][1<<maxn];
//化簡後的公式爲dp[u] = 5 / cnt + (求和 map[u][i] + dp[i]) / cnt
int dfs(int st,int u){//u是當前點.如st=0110,那麼這個狀態是去過1,2兩個點的
	if (st==(1<<n)-1){//如果已經去過了所有的點,表示從某點出發能到達所有的點
		dp[u][st]=0;
		return 1;
	}
	if (vis[u][st]) return dp[u][st]>eps;
	int cnt=0;
	dp[u][st]=5;
	vis[u][st]=true;
	for(int i=0;i<n;i++)
		if (mp[u][i]&&dfs(st|(1<<i),i)&&(!(st&(1<<i)))){//最後一個條件判斷當前點是否去過。
			int st0=st|(1<<i);
			cnt++;
			dp[u][st]+=mp[u][i]+dp[i][st0];
		}
	if (cnt){
		dp[u][st]/=cnt;
		return 1;
	}
	dp[u][st]=0;//從某點出發不能到達所有的點
	return 0;
}

int main(){
	//input;
	scanf("%d",&t);
	for(int Case=1;Case<=t;Case++){
		memset(dp,0,sizeof(dp));
		memset(mp,0,sizeof(mp));
		memset(vis,false,sizeof(vis));
		scanf("%d%d",&n,&m);
		int u,v,w;
		while(m--){
			scanf("%d%d%d",&u,&v,&w);
			mp[u][v]=mp[v][u]=1.0*w;
		}
		dfs(1,0);
		printf("Case %d: %.10lf\n",Case,dp[0][1]);
	}
	return 0;
}
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
double mapp[20][20];
double dp[20][1 << 16];
bool vis[20][1 << 16];
int n;
double  dfs(int u,int st)
{
    if(st == (1 << n) - 1)
    {
        dp[u][st] = 0;
        return 1;
    }
    if(vis[u][st] == true)
        return dp[u][st];
    dp[u][st] = 5.0;
    vis[u][st] = true;
    int cnt = 0;
    for(int i = 0; i < n; i ++)
    {
        if(mapp[u][i] !=  0 && ( !(st & (1 << i))) && dfs(i,st | (1<<i)))//保證當前點之前是沒有走過的,增加當前點dfs的結果不是零即可
        {
            cnt ++;
            dp[u][st] += mapp[u][i] + dp[i][st | (1<<i)];

        }
    }
    if(cnt != 0)
    {
        dp[u][st] /= cnt;
        return 1.0;
    }
    dp[u][st] = 0;
    return 0;
}
int main()
{
  //  freopen("in.txt","r",stdin);
    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        memset(dp,0,sizeof(dp));
        memset(mapp,0,sizeof(mapp));
        memset(vis,false,sizeof(vis));
        int m,u,v,w;
        scanf("%d%d",&n,&m);
        for(int i = 0; i < m; i ++)
        {
            scanf("%d%d%d",&u,&v,&w);
            mapp[u][v] = w * 1.0;
            mapp[v][u] = w * 1.0;
        }
        dfs(0,1);
        printf("%f\n",dp[0][1]);

    }
    return 0;
}

L.

題意:n個人扔球任意投入到m個框中,投k次。每個人每次將球投入到任意一個框中的概率爲p(也就是說要對準一個框投入的概率爲p)問投完後所有框中球的個數之和的期望。

解析:水題。一個人投一次投入到一個框中的球的期望爲p,投入到m個框中依舊爲p(框是任意的,投完加和) 投k次期望爲kp,n個人期望爲nkp。簡單相乘就行

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;

int main()
{
   // freopen("in.txt","r",stdin);
    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int n,m,k;
        double p;
        scanf("%d%d%d%lf",&n,&m,&k,&p);
        printf("%.8f\n",n * k * p);

    }
    return 0;
}
M

題意:T組數據。n個點,m條邊,S大小的數據,沒一個大小的數據來回需要2*k的時間。m條邊,每條邊都有一個pi表示發送成功的概率。是雙向邊。如果發送失敗則需要從新發送。每次發送每一個大小的無論成功與否都要耗費2 * k的時間。求發送數據從0點到n - 1最小的期望時間。

解析:因爲要求的是最小的期望時間,首先要求出從0 到n - 1,發送成功概率最大的路。由於n較小利用floyd即可。發送一次成功的概率爲p。那麼對於一個數據大小的發送成功的期望時間爲2 * k/ p,對於s大小的數據來說則是 2 * k * s / p

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
//pay attention to use long long
double d[105][105];
int n;
void Floyd()
{
    for(int k = 0; k < n; k ++)
    {
        for(int i = 0; i <n; i ++)
        {
            for(int j = 0; j < n; j ++)
            {
                d[i][j] = max(d[i][j],d[i][k] * d[k][j]);
            }
        }
    }
}
int main()
{
  // freopen("in.txt","r",stdin);

    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        memset(d,0,sizeof(d));
        int m,k,u,v,p;
        long long s;
        scanf("%d%d%I64d%d",&n,&m,&s,&k);
        for(int i = 0; i < m; i ++)
        {
            scanf("%d%d%d",&u,&v,&p);
            d[u][v] = p * 0.01;
            d[v][u] = p * 0.01;
        }
        Floyd();
        printf("%f\n",s * k * 2.0 / d[0][n - 1] );


    }
    return 0;
}

N.(不理解)

題意:n個棍子,一種棍子可以和其它的棍子區別開來,一種不能。每根棍子有一個重量。一個人每次選擇一根棍子,爲了使得每根棍子都至少被選擇過一次,求棍子重量的期望。

解析:又見優惠劵收集問題。如果只有不能和其他棍子區別開來的棍子,那麼棍子的期望是可以算出來的。

但此題是按照貢獻的期望數來計算的,能區分開的棍子貢獻的期望數爲棍子的重量本身。

按照全部都是不能區分開,需要抽n * Hn次(Hn爲調和級數),這些次數抽到的棍子重量的期望爲n * Hn * (總重量)/ n,那麼對於每根不可區分的棍子來說,對期望的貢獻時間爲Hn * 棍子的重量。而對於可以區分的期望只能爲棍子的重量

思路實際上是認爲所有的棍子都是抽到後放回的(不能區分開的),來求期望數。抽到之後,還能再抽。但在能區分開的棍子的期望時則只計算一次

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
double H[5005];
int main()
{
   // freopen("in.txt","r",stdin);
   for(int i = 1; i <= 5005; i ++)
   {
       H[i] = H[i - 1] + 1.0 / i;
   }
    int T;
    scanf("%d",&T);
    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);

        int n,a,b;
        scanf("%d",&n);
        double ans= 0;
        for(int i = 0; i < n; i ++)
        {
            scanf("%d%d",&a,&b);
            if(b == 1)
            {
                ans += a;
            }
            else
            {
                ans += H[n] * a;
            }
        }
        printf("%.6f\n",ans);
    }
    return 0;
}
O.

題意:一套撲克54張牌,其中大小王爲混子。問使得你手上每一種花色都大於等於給定數目,需要發多少張牌的期望。如果不可能,則輸出-1
解析:概率dp + 狀態壓縮 + 記憶化搜索

dp[a][b][c][d][5][5] // 前4項分別表示未發給你的各種花色的數量。第五,六維 = 0 表示你沒有大小王,1,2,3,4表示大小王被認爲是這四種不同的花色

表示從當前狀態開始達到要求狀態還需要發牌的數量

dp[a][b][c][d][x1][x2] += dp[a - 1][b][c][d][x1][x2] * a / tot (另:b,c,d)

tot 爲牌的總數,注意考慮是否有大小王

另外如果大小王還沒有用,就要遍歷讓大小王等於四種花色中的期望數最小的一個

dp[a][b][c][d][x1][x2] += dp[a][b][c][d][i][x2]  / tot (大小王只有一張牌所以 * 1.0 省略)

dp[a][b][c][d][x1][x2] += 1.0;//總排數加1

邊界條件當你手上的各種牌已經滿足條件了,返回零

將每個要求的牌數大於13的部分加起來如果大於2,則是違法的輸出-1

代碼:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
double  dp[20][20][20][20][5][5];//注意是double 類型的
int q[10];
int A,B,C,D;


double dfs(int a,int b,int c,int d,int x1,int x2)
{
    double ans= dp[a][b][c][d][x1][x2];
    if(ans > 0)
        return ans;
    q[1] =13 - a,q[2] =13 - b,q[3] = 13 - c,q[4] = 13 - d;
    q[x1] ++,q[x2] ++;
    if(q[1] >= A && q[2] >= B && q[3] >= C &&q[4] >=D)
        return 0;

    int tot = a + b + c + d + (x1 == 0? 1:0) +  (x2 == 0? 1 :0);//如果 x== 0表示王海沒用過,總排數加1
    if(a > 0) ans += 1.0 * a / tot * dfs(a - 1,b,c,d,x1,x2);
    if(b > 0)ans += 1.0 * b / tot * dfs(a,b - 1,c,d,x1,x2);
    if(c > 0)ans += 1.0 * c / tot * dfs(a,b,c - 1,d,x1,x2);
    if(d > 0) ans += 1.0 * d / tot * dfs(a,b,c,d - 1,x1,x2);
    if(x1== 0)//如果大小王還沒用,就有概率抽到大小王,收到大王的概率爲 1.0 /tot
    {
        double t = 100000;
        for(int i = 1; i <= 4; i ++)
        {
            t = min(t,dfs(a,b,c,d,i,x2));
        }
        ans += 1.0 * t / tot;
    }
    if(x2== 0)
    {
        double  t = 100000;
        for(int i = 1; i <= 4; i ++)
        {
            t = min(t,dfs(a,b,c,d,x1,i));
        }
        ans += 1.0 * t / tot;
    }
    ans += 1.0;
    dp[a][b][c][d][x1][x2] = ans;
    return ans;
}
int main()
{
    //  freopen("in.txt","r",stdin);

    int T;
    scanf("%d",&T);

    for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        scanf("%d%d%d%d",&A,&B,&C,&D);
        int cnt= 0;//注意計算的是超過13的總個數,沒超過的不要加起來
        if(A > 13)
            cnt+= A - 13;
        if(B > 13)
            cnt+= B - 13;
        if(C > 13)
            cnt+= C - 13;
        if(D> 13)
            cnt+= D - 13;
        if(cnt > 2)
        {
            printf("-1\n");
            continue;
        }
        memset(dp,0,sizeof(dp));

        printf("%.8f\n",dfs(13,13,13,13,0,0));
    }
    return 0;
}

P.(完全且依舊不理解)(網上沒題解啦(;′⌒`))

題意:再見迷宮(很眼熟啦)區別就是你會記住你選擇的過去的k 個門,而不會再打開,問走出迷宮的期望。

解析:題面的類似,和做法的不同。。。

首先還是要判斷是否能走出去。然後dfs(cnt1,cnt2,t) 三個參數分別是有cnt1個正數,cnt2個負數,和已經走了t步

一般情況下 return cnt1 / (cnt1 + cnt2) * av1 + (dfs(cnt1,cnt2 - 1,t + 1) + av2) * cnt2 / (cnt1 + cnt2)

如果cnt2<= 0 return av1

如果t >= k即已經記住了k個門了,不會再多了。那麼此種情況和A題是一樣的 直接就能求出結果return 1.0 *(c1 * av1 + c2 * av2 )/ c1;

代碼:

//注意兩個整數相處得到一個double型要乘1.0
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
int k;
        int sum1 ,sum2 ;
        int cnt1,cnt2;
        double av1,av2 = 0;
double dfs(int c1,int c2,int t)
{
    if(c2 <= 0)
        return av1;
    if(t >= k)
    {
        return 1.0 *(c1 * av1 + c2 * av2 )/ c1;
    }
    return 1.0 * c1 /(c1 + c2) * av1 + 1.0 * c2/(c1 + c2) *(dfs(c1,c2 - 1,t + 1) + av2);
}
int main()
{
	//freopen("in.txt","r",stdin);
	int T,a;
	scanf("%d",&T);
	for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int n;
        sum1 = 0,sum2 = 0,cnt1= 0,cnt2 = 0,av1 = 0,av2 = 0;
        scanf("%d%d",&n,&k);
        for(int i = 0; i < n; i ++)
        {
            scanf("%d",&a);
            if(a> 0)
            {
                sum1 += a;
                cnt1 ++;
            }
            else
            {
                sum2 += -1 * a;
                cnt2 ++;
            }
        }
       if(cnt1) av1 =1.0* sum1 / cnt1;
       if(cnt2) av2 =1.0 *sum2 / cnt2;
        if(cnt2 == n)
        {
            printf("-1\n");
        }
        else
        {
            printf("%.8f\n",dfs(cnt1,cnt2,0));
        }
    }

	return 0;
}

Q.(最初表示不夠理解)中間推導公式不夠理解

參照的題解:http://blog.csdn.net/yeyeyeguoguo/article/details/46446905

http://blog.csdn.net/whyorwhnt/article/details/9904151

題意:投中求的概率爲p.如果連續投不中球k1次,或連續投中球k2次練習結束,求投球數的期望

解析:f(i)表示連續投中i次後剩餘投球個數的期望數,g(i)表示連續投不中i次的期望數,剩餘投球個數的期望數

注意:求期望數別忘記加1

f(i) = f(i + 1) * p  + g(1) * (1 - p)  + 1

g(i) = g(i + 1) * ( 1 -p) + f(1) * p + 1

已知邊界f(k2) = 0,g(k1) = 0

可以求出f(1),g(1):推導過程

設x = g(1) * (1-p) + 1.不要按通解來求

f(i) = f(i + 1) * p + x.

f(k2) = 0,f(k2 -1) = x,f(k2 - 2) = p*x + x,f(k2 -3) = p * p * x + p *x + x

顧f(1) = p^k - 2 * x +  p ^ k - 3 * x +……+p ^ 0*x

f(1) =  (1 - p^(k - 1)) / (1 - p) * (g(1) * (1-p) + 1)

求出g(1) 。兩方程聯立求解

f(0) = f(1) * p + g(1) * (1-p) + 1,g(0) = ……

最後答案爲f(0) + g(0)

代碼:

//注意兩個整數相處得到一個double型要乘1.0
#include <iostream>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
#include <algorithm>
#include <list>
#include <map>
#include <vector>
#include <queue>
#include <stack>
#include <cmath>
#include <cstdlib>
using namespace std;
int k;
        int sum1 ,sum2 ;
        int cnt1,cnt2;
        double av1,av2 = 0;
double dfs(int c1,int c2,int t)
{
    if(c2 <= 0)
        return av1;
    if(t >= k)
    {
        return 1.0 *(c1 * av1 + c2 * av2 )/ c1;
    }
    return 1.0 * c1 /(c1 + c2) * av1 + 1.0 * c2/(c1 + c2) *(dfs(c1,c2 - 1,t + 1) + av2);
}
int main()
{
	//freopen("in.txt","r",stdin);
	int T,a;
	scanf("%d",&T);
	for(int t = 1; t <= T; t ++)
    {
        printf("Case %d: ",t);
        int n;
        sum1 = 0,sum2 = 0,cnt1= 0,cnt2 = 0,av1 = 0,av2 = 0;
        scanf("%d%d",&n,&k);
        for(int i = 0; i < n; i ++)
        {
            scanf("%d",&a);
            if(a> 0)
            {
                sum1 += a;
                cnt1 ++;
            }
            else
            {
                sum2 += -1 * a;
                cnt2 ++;
            }
        }
       if(cnt1) av1 =1.0* sum1 / cnt1;
       if(cnt2) av2 =1.0 *sum2 / cnt2;
        if(cnt2 == n)
        {
            printf("-1\n");
        }
        else
        {
            printf("%.8f\n",dfs(cnt1,cnt2,0));
        }
    }

	return 0;
}



發佈了132 篇原創文章 · 獲贊 17 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章