hihocoder 1166 交换代数|HDU 4870 Rating 等(区间翻转,高斯消元求期望)

少女幽香这几天正在学习交换代数,然而她什么也没有学会,非常痛苦。于是她开始玩起了一个简单的小游戏,来放松一下。
地面上一共有n个球,一开始有一些是黑色的,有一些是白色的。每次她随机选择一个区间(一共有n(n+1)/2个区间,每个区间有相等的概率被选择),把这个区间的颜色反转,即将该区间中白球变黑球,黑球变白球。
现在她想要知道期望情况下,多少次反转能够使得整个区间都是白色的。

输入

第一行n (1 <= n <= 20),表示球的数量。
接下来一行n个数,表示这些球的颜色,0表示白色,1表示黑色。

输出

一行一个实数,表示答案。你的答案与标准答案的绝对误差在10-4以内就算正确。

样例输入
2
0 1
样例输出
3.000000
http://hihocoder.com/problemset/problem/1166
看了5天终于看懂了……题解:

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],

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <iostream>  
  2. #include <cmath>  
  3. #include <stdio.h>  
  4. using namespace std;  
  5. double dp[22];  
  6. double ans[22][22];  
  7.   
  8. int main()  
  9. {  
  10.     double p;  
  11.     while (cin>>p)  
  12.     {  
  13.         dp[0]=1/p;  
  14.         dp[1]=1/p/p;  
  15.         for(int i=2;i<20;++i)  
  16.             dp[i] = 1+(1-p)/p*(dp[i-2]+dp[i-1]+1);   
  17.         ans[0][0]=0;  
  18.         for (int i=0;i<20;++i)  
  19.         {  
  20.             ans[i+1][i]=ans[i][i]+dp[i];  
  21.             ans[i+1][i+1]=ans[i+1][i]+dp[i];  
  22.         }  
  23.         printf("%.6lf\n",ans[20][19]);  
  24.     }  
  25. }  
更简单的方法: (转)

先考虑只注册一个帐号的情况(求的是初始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 }
复制代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值