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)
一共有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;
}