少女幽香这几天正在学习交换代数,然而她什么也没有学会,非常痛苦。于是她开始玩起了一个简单的小游戏,来放松一下。
地面上一共有n个球,一开始有一些是黑色的,有一些是白色的。每次她随机选择一个区间(一共有n(n+1)/2个区间,每个区间有相等的概率被选择),把这个区间的颜色反转,即将该区间中白球变黑球,黑球变白球。
现在她想要知道期望情况下,多少次反转能够使得整个区间都是白色的。
输入
第一行n (1 <= n <= 20),表示球的数量。
接下来一行n个数,表示这些球的颜色,0表示白色,1表示黑色。
输出
一行一个实数,表示答案。你的答案与标准答案的绝对误差在10-4以内就算正确。
2 0 1
3.000000http://hihocoder.com/problemset/problem/1166
http://media.hihocoder.com/contests/challenge11/hihoCoderChallenge11SolutionsB-D.pdf
#include<iostream>
#include<algorithm>
#include<string>
#include<map>
#include<vector>
#include<cmath>
#include<queue>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#define ll long long
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
typedef double LD;
int n,b[30],c,cnt;
LD g[30][30];
/*
void gauss(int n){ //①
rep(i,0,n) {
int p=i;
rep(j,i+1,n) if (fabs(g[j][i])>=fabs(g[p][i])) p=j;
rep(j,i,n+1) swap(g[i][j],g[p][j]);
if (fabs(g[i][i])<=1e-9) continue;
rep(k,i+1,n+1) g[i][k]/=g[i][i]; g[i][i]=1;
rep(j,i+1,n) {
LD cof=-g[j][i];
rep(k,i,n+1) g[j][k]+=cof*g[i][k];
}
}
per(i,0,n) {
rep(j,0,i) g[j][n]-=g[j][i]*g[i][n];
}
}
void gauss() { //②
int already = 0;
for(int i = 0; i < n; i++) {
int no = already;
for(int j = already; j < n; j++) if(fabs(g[j][i]) > fabs(g[no][i]))
no = j;
if(fabs(g[no][i]) ==0 ) continue;
for(int j = 0; j <= n; j++)
swap(g[no][j], g[already][j]);
for(int j = 0; j < n; j++) if(j != already){
double flag = g[j][i] / g[already][i];
for(int k = 0; k <= n; k++)
g[j][k] -= g[already][k] * flag;
}
already++;
}
}
*/
void gauss(int n){ //③
int r;
for(int i=0;i<n;++i){
r=i;
for(int j=i+1;j<n;++j)
if(fabs(g[j][i])>fabs(g[r][i])) r=j;
if(fabs(g[r][i]==0)) continue;
if(r!=i){
for(int j=0;j<=n;++j)
swap(g[r][j],g[i][j]);
}
/*
根据精度需要选择以下其一:
低精度
for (k = i + 1; k < n; k++) {
r = a[k][i] / a[i][j];
for (j = i; j <= n; j++)
a[k][j] -= r * a[i][j];
}
高精度
*/
for(int j=n;j>=i;--j){
for(int k=i+1;k<n;++k)
g[k][j]-=g[k][i]/g[i][i]*g[i][j];
}
}
for(int i=n-1;i>=0;--i){
for(int j=i+1;j<cnt;++j)
g[i][cnt]-=g[j][cnt]*g[i][j];
g[i][cnt]/=g[i][i];
}
}
int main() {
scanf("%d",&n);
g[0][0]=1;
int p=(n+1)/2+1; //必须+1,,这是规定好的
for (int i=2;i<=n+1;i+=2) {
g[i/2][i/2]=1; //不能少
g[i/2][i/2-1]-=1.*i*(i-1)/n/(n+1); //表示1的个数从i个变成了i-2个,i*(i-1)/2表示从i个1里面选两个(2和后面约掉了)
g[i/2][i/2]-=2.*i*(n+1-i)/n/(n+1); //一个0变1,一个1变0,可能性有i*(n+1-i),n*(n+1)/2表示从n+1位里选两位
g[i/2][i/2+1]-=1.*(n+1-i)*(n-i)/n/(n+1); //原本应该是g[i][i+2],但因为数字的下标必须是连续的,从n+1-i个0里面选两个
g[i/2][p]=1; //不能少
}
rep(i,1,n+1) scanf("%d",&b[i]);
rep(i,1,n+2) c+=b[i]!=b[i-1]; //计算有几个1 (肯定是偶数,这也是上面为什么能写成/2)
n=p;
cnt=n;
gauss(n);
// printf("%.10f\n",(double)g[c/2][p]/g[c/2][c/2]); // ②用这个
printf("%.10f\n",(double)g[c/2][p]); //①和③用这个
return 0;
}
题意:对于某项比赛,取得前200名,他的分数将为min(x+50,1000),反之为max(x-100,0).现在,一个人参加该项比赛,他取得前200名的概率为P。他拥有两个账号,每个账号的初始分数为0,。每次比赛他拿分数最少的账号参加比赛。求他参加比赛场数的期望,使其中的一个账号到达1000分。
思路:期望问题,我们这里利用高斯消元来求期望。
设他当前两个账号的分数分别为i,j,设dp[i][j]表示分数为i,j时到达目标时期望的比赛场数。则dp[i][j] = p * dp[i][j+50]+(1-p) * dp[i][j-100]+1.且dp[20][19] = 0;
变形可得:dp[i][j] - p * dp[i][j+50] - (1-p) * dp[i][j-100] = 1.0
如果我们把50看成单位分数的话,这样会出现210个变量,可以利用高斯消元来求解。 (转)
http://acm.hdu.edu.cn/showproblem.php?pid=4870
#include<iostream>
#include<algorithm>
#include<string>
#include<map>
#include<vector>
#include<cmath>
#include<queue>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
#define ll long long
using namespace std;
const int MAX = 21;
const int MAXN = 500;
const double EPS = 1e-6;
int idx[MAX][MAX];
double a[MAXN][MAXN];
//a is a matrix , l is represent free variable, ans store the answer
double gauss(int N) {
int n = N;
for (int i = 0; i < n; i++) {
int r;
for (r = i; r < n; r++)
if (fabs(a[r][i]) >= EPS)
break;
if (r == n) continue;
for (int j = 0; j <= n; j++) swap(a[i][j], a[r][j]);
for (int j = n; j > i; j--) a[i][j] /= a[i][i];
a[i][i] = 1.0;
for (int j = 0; j < n; j++) {
if (i == j) continue;
if (fabs(a[j][i]) >= EPS) {
for (int k = n; k > i; k--)
a[j][k] -= a[j][i] * a[i][k];
a[j][i] = 0.0;
}
}
}
return a[idx[0][0]][N];
}
int main(void){
int cnt = -1;
for(int i = 0; i <= 20; ++i)
for(int j = 0; j <= i; ++j)
idx[i][j] = ++cnt;
cnt=cnt+1;
double P;
while(~scanf("%lf",&P)){
memset(a,0,sizeof(a));
for(int i = 0; i < 20; ++i){ //注意这里没有等号(i=20已经完成任务了,不需要再计算)
for(int j = 0; j < i; ++j){ //j比i小时,选j参加比赛
a[idx[i][j]][cnt] = 1.0;
a[idx[i][j]][idx[i][j]] = 1.0;
a[idx[i][j]][idx[i][j+1]] -= P; //+50/50=+1 从i分j分变成i分j+1分的概率
a[idx[i][j]][idx[i][max(j-2,0)]] -= 1.0 - P; //-100/50=-2,注意max(i-2,0)
}
a[idx[i][i]][cnt] = 1.0; //j==i是个特殊情况,如果赢了,i+1,输了,j-2(始终要保证i>=j)
a[idx[i][i]][idx[i][i]] = 1.0;
a[idx[i][i]][idx[i+1][i]] -= P;
a[idx[i][i]][idx[i][max(i-2,0)]] -= 1.0 - P;
}
printf("%f\n",gauss(cnt));
}
return 0;
}
2、用dp
首先离散化,因为每场比赛分数的变化都是50的倍数,令每场赢了得1分,输了扣2分。
dp[i]表示单场比赛从i分数提高到i+1的分数的期望值,
则有:dp[i]=p+(1-p)(dp[i-2]+dp[i-1]+dp[i]+1)
==>dp[i]=1/p+(1-p)/p*(dp[i-2]+dp[i-1]);dp[0]=1/p,dp[1]=1/p/p;
用ans[i][i]表示两个账号分数从0打到ii的期望,对于账号分数的上升,他们是交错上升的,意思是当他们分数一样的时候,前面的赢一分,当前面的赢了一分之后,下一场就后面的赢,所以只需要维护ans[i+1][i] 和ans[i+1][i+1],且
ans[i+1][i]=ans[i][i]+dp[i],ans[i+1][i+1]=ans[i+1][i]+dp[i],
- #include <iostream>
- #include <cmath>
- #include <stdio.h>
- using namespace std;
- double dp[22];
- double ans[22][22];
- int main()
- {
- double p;
- while (cin>>p)
- {
- dp[0]=1/p;
- dp[1]=1/p/p;
- for(int i=2;i<20;++i)
- dp[i] = 1+(1-p)/p*(dp[i-2]+dp[i-1]+1);
- ans[0][0]=0;
- for (int i=0;i<20;++i)
- {
- ans[i+1][i]=ans[i][i]+dp[i];
- ans[i+1][i+1]=ans[i+1][i]+dp[i];
- }
- printf("%.6lf\n",ans[20][19]);
- }
- }
先考虑只注册一个帐号的情况(求的是初始e[0],即0到20的期望,有e[20]=0)
e[0] = p*e[1]+(1-p)*e[0] +1 ==> e[0] = e[1] +1/p
e[1] = p*e[2]+(1-p)*e[0] +1 ==> e[0] = e[2] +1/p+1/(p*p)
e[2] = p*e[3]+(1-p)*e[0] +1
e[n-1] = p*e[n]+(1-p)*e[n-3]+1
e[n] = 0
显然,可以知道,e[0] = e[k] + t[k]。(因为每一次代入后,e[0]跟e[k]都会是乘上系数p)
代入一般情况下的,e[k] = p*e[k+1]+(1-p)*e[k-2]+1,-t[k] = p*(-t[k+1])+(1-p)*(-t[k-2])+1
所以有了t[0]=0,t[1]=1/p,t[2]=1/(p*p)以及t[k],t[k+1],t[k-2]的关系,可以求出所有t[i]
而2个帐号的时候,由于每次取rating小的参赛,必然是这样的结果(0,0)=>(1,0)=>(1,1)=>(2,1)=>....=>(19,19)=>(20,19)
而t[k]表示的是一个帐号从0到达k的期望时间,所以答案上t[20]+t[19]。
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int main(){
double p,t[22];
while(~scanf("%lf",&p)){
t[0]=0,t[1]=1./p,t[2]=1./p+1./(p*p);
for(int i=3;i<=20;++i) t[i] = (1./p+1./p*t[i-1]-(1-p)/p*t[i-3]);
printf("%.6lf\n",t[19]+t[20]);
}
return 0;
}
附加转载的两题:
http://acm.zjut.edu.cn/ShowProblem.aspx?ShowID=1423
题解:http://www.cnblogs.com/keanuyaoo/archive/2013/09/04/3301559.html
Description: (转)
由于山体滑坡,DK被困在了地下蜘蛛王国迷宫。为了抢在DH之前来到TFT,DK必须尽快走出此迷宫。此迷宫仅有一个出口,而由于大BOSS的力量减弱影响到了DK,使DK的记忆力严重下降,他甚至无法记得他上一步做了什么。所以他只能每次等概率随机的选取一个方向走。当然他不会选取周围有障碍的地方走。如DK周围只有两处空地,则每个都有1/2的概率。现在要求他平均要走多少步可以走出此迷宫。
Input:
先是一行两个整数N, M(1<=N, M<=10)表示迷宫为N*M大小,然后是N行,每行M个字符,'.'表示是空地,'E’表示出口,'D’表示DK,'X’表示障碍。
Output:
如果DK无法走出或要超过1000000步才能走出,输出tragedy!,否则输出一个实数表示平均情况下DK要走几步可以走出迷宫,四舍五入到小数点后两位。
Sample Input:
1 2 ED 3 3 D.X .X. X.E
Sample Output:
1.00 tragedy!
首先对地图节点重新标号。
假设E[i]表示DK从i点开始走出迷宫的期望值。那么E[i]=(E[a1]+E[a2]+E[a3]+...+E[an])/n+1,其中a1...an是i的相邻节点。
那么对于每一个DK可达的节点来说,都可以为它建立这样的一个方程。
现在假设DK可达的点有N个,那么我们最终将会得到N元一次方程组。最后利用高斯消元解出E[No[S]]。其中S是DK的起点,No[S]是重标号后的起点
这里要重点注意的是,我们联立方程的时候,一定要注意DK可达这个条件,不然就会导致无解的情况。
#include<iostream> #include<string> #include<cmath> #include<algorithm> using namespace std; #define MAXN 800 double mat[MAXN][MAXN]; char map[40][40]; int num[40][40]; struct node { int x; int y; }; node Star,end; int n,m,len; int di[4][2]={1,0,0,1,-1,0,0,-1}; int cut; int isok(int x,int y) { return x >= 0 && x < n && y >= 0 && y < m && map[x][y]!='X'; } node queue[2*MAXN]; int head,tail; void fillnum(int x,int y) //对地图进行重新标号,标号从0开始 { num[x][y]=++cut; tail=head=0; node e,q; e.x=x;e.y=y; queue[tail++]=e; while(head<tail) { e=queue[head++]; for(int i=0;i<4;i++) { q.x=e.x+di[i][0]; q.y=e.y+di[i][1]; if(isok(q.x,q.y) && num[q.x][q.y]==-1) { num[q.x][q.y]=++cut; queue[tail++]=q; } } } } bool gauss(int n) { int i, j, row, idx; double buf, maxx; for(row = 0; row < n; row ++) { for(maxx = 0, i = row; i < n; i ++) { if(maxx < fabs(mat[i][row])) { maxx = fabs(mat[i][row]); idx = i; } } if(maxx == 0) continue; if(idx != row) { for(i = row; i <= n; i ++) swap(mat[row][i], mat[idx][i]); } for(i = row + 1; i < n; i ++) { if(fabs(mat[i][row])<1e-8) continue; buf = mat[i][row] / mat[row][row]; for(j = row; j <= n; j ++) mat[i][j] -= buf * mat[row][j]; } } for(i = n - 1; i >= 0; i --) { for(j = i + 1; j < n; j ++) mat[i][n] -= mat[i][j] * mat[j][j]; mat[i][i] = mat[i][n] / mat[i][i]; } return true; } int main() { int i,j,k,x,y; freopen("D:\\in.txt","r",stdin); while(scanf("%d%d",&n,&m)!=EOF) { len=0; for(i=0;i<n;i++) { scanf("%s",map[i]); for(j=0;j<m;j++) { if(map[i][j]=='D') { Star.x=i; Star.y=j; } else if(map[i][j]=='E') { end.x=i; end.y=j; } } } memset(num,255,sizeof(num)); cut=-1; fillnum(Star.x,Star.y); if(num[end.x][end.y]==-1) { printf("tragedy!\n"); continue; } memset(mat,0,sizeof(mat)); for(i=0;i<n;i++) //建立N元一次方程组 { for(j=0;j<m;j++) { if(num[i][j]!=-1) { int now=num[i][j]; int count=0; for(k=0;k<4;k++) { x=i+di[k][0];y=j+di[k][1]; if(isok(x,y)) { mat[now][num[x][y]]=-1; count++; } mat[now][now]=count; mat[now][cut+1]=count; } } } } x=num[end.x][end.y]; memset(mat[x],0,sizeof(mat[x])); mat[x][x]=1; if(gauss(cut+1)) { if(mat[num[Star.x][Star.y]][num[Star.x][Star.y]]<=1000000) printf("%0.2lf\n",mat[num[Star.x][Star.y]][num[Star.x][Star.y]]); else printf("tragedy!\n"); } else { printf("tragedy!\n"); } } return 0; }
http://acm.zjut.edu.cn/ShowProblem.aspx?ShowID=1317
Description:(转)
m个人位于正m边形的顶点上,彼此抛掷飞盘。他们共有两个飞盘,且开始时这两个飞盘位于相距为n的两个人的手中(相邻两个人相距为1,依此类推)。在每次抛掷时两个飞盘被同时抛出,飞盘都以1/2的概率被抛到掷飞盘的人左边相邻的人,1/2的概率被抛到右边相邻的人。此过程一直进行,直到两个飞盘被掷到同一个人手中,求此抛掷飞盘的游戏平均情况下(期望)会在抛掷几次后结束。
Input:
每行有两个整数m (2<m<=100),n (0 < n < m)。
这题我们以两个飞盘的距离为状态进行转移。
那么E[n]=E[n+2]/4+E[n-2]/4+E[n]/2+1,化简成:2E[n]-E[n+2]-E[n-2]=4。
首先对于两个飞盘给定的起始距离n,我们可以先搜索一下可否到达状态0,如果不行,则直接输出INF。在搜索的过程中,顺便把重新标号也进行了。为什么这题也要重新标号呢?
因为该题中,假设m是偶数,那么对于任意的n,n+1和n-1都是不可达的状态。请一定记得,如果方程组中有不可达的点的话,就会导致无解的情况!
接下来对每一个可达的状态建立一个如上的方程,最后用高斯消元法解除E[No[n]]即可!
1 #include<iostream> 2 #include<string> 3 #include<cmath> 4 #include<stdio.h> 5 #include<memory.h> 6 #include<algorithm> 7 using namespace std; 8 #define maxn 200 9 10 double mat[maxn][maxn]; 11 int num[maxn]; 12 int n,m; 13 int cut; 14 15 int getnum(int x) //获取x的状态,对于一个环来说,x和m-x是一样的,我们取小的 16 { 17 if(x<0) 18 x=-x; 19 if(x>n/2) 20 x=n-x; 21 return x; 22 } 23 24 void dfs(int x) //重标号 25 { 26 num[x]=cut++; 27 int y=getnum(x+2); 28 if(num[y]==-1) 29 dfs(y); 30 y=getnum(x-2); 31 if(num[y]==-1) 32 dfs(y); 33 } 34 35 bool gauss(int n) 36 { 37 int i, j, row, idx; 38 double buf, maxx; 39 for(row = 0; row < n; row ++) 40 { 41 for(maxx = 0, i = row; i < n; i ++) 42 { 43 if(maxx < fabs(mat[i][row])) 44 { 45 maxx = fabs(mat[i][row]); 46 idx = i; 47 } 48 } 49 if(maxx == 0) return false; 50 if(idx != row) 51 { 52 for(i = row; i <= n; i ++) 53 swap(mat[row][i], mat[idx][i]); 54 } 55 for(i = row + 1; i < n; i ++) 56 { 57 buf = mat[i][row] / mat[row][row]; 58 for(j = row; j <= n; j ++) 59 mat[i][j] -= buf * mat[row][j]; 60 } 61 } 62 for(i = n - 1; i >= 0; i --) 63 { 64 for(j = i + 1; j < n; j ++) 65 mat[i][n] -= mat[i][j] * mat[j][j]; 66 mat[i][i] = mat[i][n] / mat[i][i]; 67 } 68 return true; 69 } 70 71 int main() 72 { 73 int i,len; 74 freopen("D:\\in.txt","r",stdin); 75 while(scanf("%d%d",&n,&m)==2) 76 { 77 memset(num,255,sizeof(num)); 78 if(m>n/2) 79 m=n-m; 80 cut=0; 81 dfs(m); 82 // for(i=0;i<=n/2;i++) 83 // cout<<num[i]<<" "; 84 // cout<<endl; 85 if(num[0]==-1) 86 { 87 printf("INF\n"); 88 continue; 89 } 90 memset(mat,0,sizeof(mat)); 91 len=n/2; 92 int now,j; 93 for(i=0;i<=len;i++) 94 { 95 if(num[i]!=-1) 96 { 97 now=num[i]; 98 mat[now][now]=2; 99 mat[now][cut]=4; 100 j=getnum(i-2); 101 mat[now][num[j]]-=1; 102 j=getnum(i+2); 103 mat[now][num[j]]-=1; 104 } 105 } 106 j=num[0]; 107 memset(mat[j],0,sizeof(mat[j])); 108 mat[j][j]=1; 115 if(gauss(cut)) 116 { 117 printf("%0.2lf\n",mat[num[m]][num[m]]); 118 } 119 else 120 { 121 printf("INF\n"); 122 } 123 } 124 return 0; 125 }