POJ PKU 3028 Nordic 2006

第一次写C++,手生导致此题耗了两个晚上总计6个小时(好在是自己做出来的)(391ms)

题意:

有n个人,站成一个圈。每个人开枪击中别人的概率是固定的(设数组P[i]表示第i个人的命中率),从第一个人开始轮流开枪,求每个人的生还的概率是多少?(假设每人都以最有利自己的方式选择射击对象,并且当有多个目标都一样好时,随机选择其中一个(即m个好对象时,每个对象被他选中的概率为1/m)。并且,每轮都必须射击,虽然故意放弃射击机会可能增大存活的概率)。

不许自杀。

这个题其实是一个环状DP方程应该很好想

F[i,j,s]表示s状态下,轮到第j个人射击时,第i个人存活概率(i,j为原题的第i个和第j个)s为二进制表示的集合

F[i,j,s]=(sigma(F[i,next(j,k,s),s^pow[k-1]]/sigma{k的个数})*P[j]+(1-P[j])*F[i,next(j,s).s]

首先在确定的s下

令A[j]=F[i,j,s] A[j+1]=F[i,next(j,s),s] A[j+2]=F[i,next(next(j,s),s),s]  .......

b[j]=(sigma(F[i,next(j,k,s),s^pow[k-1]]/sigma{k的个数})*P[j]

Q[j]=1-P[j]

b[j+1],Q[j+1]所表示的类似于A[j+1]

那么按方程展开有A[j]=b[j]+Q[j]a[j+1]=b[j]+Q[j]b[j+1]+Q[j]Q[j+1]a[j+2]=.....................

最后由于有环,展开之后就可以回到A[j]了,且系数为Q[1]到Q[m]的累乘,然后移项相减相除,

Final  A[j]=(b[j]+Q[j]b[j+1]+Q[j]Q[j+1]b[j+2]+.......+Q[j]*Q[j+1]*...*Q[j-1]b[j-1])/(1-Q[1]至Q[m]的累乘)

而b[j]的系数也可以找到规律

算法就出来了

STEP 1

枚举S(从1到pow[n]-1)

     {

     STEP 2

    计算每个人的决策;

      STEP 3

     枚举 i{i in s}

         {STEP 4

         计算 i 下的 b[j];

          STEP 5

         枚举j (j in s)计算F[i,j,s];

          }

     }

复杂度O((n^3)*(2^n))

注意STEP 5有两重循环

每个人的答案就是F[i,1,pow[n]-1]

一下是代码

#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#define zero(a) ((fabs(a)<eps))/*注意是Fabs,不是abs*/
#define equal(a,b) (zero(((a)-(b))))
const int ppo[]={1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192};
const double eps=1e-12;
int plan[15][15];
double p[15],b[15];
double f[14][14][8192];
int head;
int que[15];
int next[15][8192];
void prepare()
{
  memset(p,0,sizeof(p));
}
void calcplan(int now,int state)/*即时计算S下now的决策,注意可能有多个选择,但没有自己!(不许自杀)*/
{
  double maxt=0;
  for(int l=1;l<=head;l++)
    if (que[l]!=now)
      {
	int kill=que[l];
	int k=next[now][state];
	if (k==kill) k=next[k][state];
	double com=f[now][k][state^ppo[kill-1]];
	if (equal(com,maxt))
	      {
		plan[now][0]++;
		plan[now][plan[now][0]]=kill;
	      }
	else
	  if (com>maxt)
		{
		  plan[now][0]=1;
		  maxt=com;
		  plan[now][1]=kill;
		}
      }
}
void ready()/*计算每个S下下个人是谁,存入next数组*/
{
  for (int state=1;state<ppo[13];state++)
    {
      head=0;
      memset(que,0,sizeof(que));
      int stmp=state;
      for(int i=1;i<=13;i++)
	    {
	      if (stmp & 1==1)
		{
		  head++;
		  que[head]=i;
		  next[que[head-1]][state]=que[head];
		};
	      stmp=stmp>>1;
	    }
      next[que[head]][state]=que[1];
      next[0][state]=0;
    }
}
int main()
{
  int task=0;
  ready();
  freopen("poj3028.in","r",stdin);
  freopen("poj3028.out","w",stdout);
  scanf("%d",&task);
  for(;task>0;task--)
    {
      prepare;
      int n;
      int i;
      scanf("%d",&n);
      for(i=1;i<=n;i++)
	{
	  scanf("%lf",&p[i]);
	  p[i]/=100;
	}
      int state;
      for(state=1;state<ppo[n];state++)
	{
	  memset(plan,0,sizeof(plan));
	  memset(que,0,sizeof(que));
	  head=0;
	  memset(b,0,sizeof(b));
	  int stmp=state;
	  int j=0;
	  double tot=1;
	  for(i=1;i<=n;i++)
	    {
	      if (stmp & 1==1)
		{
		  head++;
		  que[head]=i;
		  tot=tot*(1-p[i]);
		};
	      stmp=stmp>>1;
	    }
	  if (head==1)
	    {
	      f[que[1]][que[1]][state]=1;
	      continue;
	    }
	    /*特殊处理1个人的情况!*/
	  tot=1-tot;
	  for(i=1;i<=head;i++)
	    calcplan(que[i],state);/*记录决策计划中无自己*/    
	  for(int p1=1;p1<=head;p1++)
	    {
	      i=que[p1];
	      for(int p2=1;p2<=head;p2++)/*计算i下的B[j]*/
		{
		  j=que[p2];
		  double sum=0;
		  for(int p3=1;p3<=plan[j][0];p3++)
		    {
		      int k=next[j][state];
		      if (k==plan[j][p3]) k=next[k][state];
		      sum+=f[i][k][state^ppo[plan[j][p3]-1]];
		    }
		  sum/=plan[j][0];
		  b[p2]=sum*p[j];
		}
	      for(int p2=1;p2<=head;p2++)/*计算F[i,j,s]*/
		{
		  j=que[p2];
		  double sum=0;
		  double times=1;
		  for(int p3=p2;p3<=head;p3++)
		    {
		      sum+=b[p3]*times;
		      times*=(1-p[que[p3]]);
		    }
		  for(int p3=1;p3<p2;p3++)
		    {
		      sum+=b[p3]*times;
		      times*=(1-p[que[p3]]);
		    }
		  f[i][j][state]=sum/tot;
		}
	    }
	}
      for (int i=1;i<=n;i++)
	{
	  printf("%.2f ",f[i][1][ppo[n]-1]*100.0);
	}
      printf("\n");
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值