poj 3028 Shoot-out 概率dp ★★

题意:

n个枪手站在一起,轮流射击,每次只能射一发,且只能射自己以外的人,命中即死。每个人每次射击都有一个命中率。游戏到剩下最后一个人存活结束。每个人每次射击会用最优决策(决策后,自己存活率最高),如果有多个最优决策,随机。

问n(n<=13)个人存活的概率。


:

状态的表示是[i][S][j]表示当前存活的状态S,i表示当前该谁射击,j表示此时j的胜率。

状态转移存在环,[i][S]这个状态可能多次转移回到[i][S]

设b[i][k]为i命中时k的胜率,a[i][k]为i未命中k的胜率,p[i]为i命中的概率,q[i]为i未命中的概率。

则dp[cur][state][k]=p[cur]*b[cur][k]+q[cur]*a[cur][k]

b[cur][k]比较好算,先求出每个子状态,然后统计最优解的个数。
a[cur][k]=a[cur+1][k]*q[cur+1]+b[cur+1][k]*p[cur+1]
= (a[cur+2][k]*q[cur+2]+b[cur+2][k]*p[cur+2])*q[cur+1]+b[cur+1][k]*p[cur+1]
=a[cur][k]* ( q[cur+1] * …*q[cur])+
b[cur+1][k]*p[cur+1]+b[cur+2][k]*(q[cur+1]*p[cur+2])+…+b[cur][k]*(q[cur+1]* … *q[cur-1])*p[cur]

#include
#include
#include
#include
#include
#include
#include
using namespace std;

#define all(x) (x).begin(), (x).end()
#define for0(a, n) for (int (a) = 0; (a) < (n); (a)++)
#define for1(a, n) for (int (a) = 1; (a) <= (n); (a)++)
#define mes(a,x,s)  memset(a,x,(s)*sizeof a[0])
#define mem(a,x)  memset(a,x,sizeof a)
#define ysk(x)  (1<<(x))
typedef long long ll;
typedef pair<int, int> pii;
const int INF =0x3f3f3f3f;
const int maxn= 13   ;
const int maxS= 8192   ;
const double eps=1e-10;

double p[maxn+10],dp[maxn+3][maxS+3][maxn+3];
int n,ed,nex[maxn+3][maxS+3],num[maxS+3];

int next(int cur,int &state)
{
    if(~nex[cur][state]) return nex[cur][state];
    int &ans=nex[cur][state];
    while(1)
    {
        if(++cur==n) cur=0;
        if(state&ysk(cur))  return ans=cur;
    }
}
int cal(int s)
{
    if(~num[s]) return num[s];
    int cnt=0;
    while(s)
    {
        if(s&1) cnt++;
        s>>=1;
    }
    return num[s]=cnt;
}
int dcmp(double x) { if(x>eps)  return 1;  return x<-eps?-1:0;     }

double b[maxn+3][maxn+3];int c[maxn+3][maxn+3];
void dfs(int cur,int state)
{
    if(dp[cur][state][0]>=0)  return ;//很重要,必须记忆化,虽然没有环,不等于没有子问题重叠
    if(cal(state)==1)
    {
        for0(k,n) dp[cur][state][k]= ( state&ysk(k) )?1:0;
        return;
    }
    for0(i,n) if(state&ysk(i))
    {
        for(int tag=next(i,state);tag!=i  ;tag=next(tag,state)  )
        {
            int ns=state^(ysk(tag));
            dfs(next(i,ns),ns );
        }
    }
    for0(i,n)  for0(k,n)  b[i][k]=c[i][k]=0;
    for0(i,n)  if(state&ysk(i))
    {
        double maxP=0;
        for(int tag=next(i,state);tag!=i;tag=next(tag,state))
        {
            int ns=state^(ysk(tag));
            maxP=max(maxP,dp[next(i,ns)][ns][i]  );
        }
        for(int tag=next(i,state);tag!=i;tag=next(tag,state))
        {
            int ns=state^(ysk(tag));int shooter=next(i,ns);
            if(dcmp(dp[shooter][ns][i]-maxP)==0)
            {
                for0(k,n)/*这句话if(ysk(k)&ns)不能加,因为即使此时dp[shooter][ns][k]为0
               但是,可能k不同时,dp[shooter][ns][i]与此时相等,但是dp[shooter][ns][k]不为0。
               故无论dp[shooter][ns][k]为何值,均要统计。
                */
               {
                  c[i][k]++;
                  b[i][k]+=dp[shooter][ns][k];
               }
            }
        }
        for0(k,n)  if(c[i][k])  b[i][k]/=c[i][k];
    }

    for0(k,n)  dp[cur][state][k]=p[cur]*b[cur][k];
    for0(k,n)  if(state&ysk(k))
    {
        int now=cur;double tmp=1,sum=0;
        do
        {
           now=next(now,state);
           sum+=b[now][k]*tmp*p[now];
           tmp*=(1-p[now]);

        }while(now!=cur);
        dp[cur][state][k]+= (1-p[cur])*sum/(1-tmp);
    }
}
int main()
{
   int T;scanf("%d",&T);
   mem(nex,-1);
   mem(num,-1);
   while(T--)
   {
      scanf("%d",&n);for0(i,n)  {scanf("%lf",&p[i]); p[i]/=100;}
      ed=ysk(n)-1;
      for0(i,n)  for0(s,ed+1)  for0(k,n)  dp[i][s][k]=-1;
      dfs(0,ed );
      for0(i,n)  printf("%.2f%c",100*dp[0][ed][i],i==n-1?'\n':' ');
   }
   return 0;
}



你可能感兴趣的:(ACM_动态规划,概率dp)