一些和期望有关的题目

bzoj 4008
这题上来就很蒙啊…因为前面打的牌后面就不能再打了,似乎要用状压一类的东西?然后发现原来可以单独地对每张牌计算贡献,在每个时刻,就考虑单张牌是否发动,相当于是人为地安排了发动的顺序(因为顺序在这题中并不影响),发动地话就会占据一个时刻,不发动就把机会留给下一张牌,如果考虑到第N张牌还不发动,就会推到第N+1张牌,但我们并没有第N+1张,也就是轮空,可以预先处理好每张牌在一段时刻中发动的概率。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=1000+10;
double f[maxn][maxn],ans,p[maxn],hm[maxn][maxn];
int d[maxn],n,r,T;
void init()
{
   ans=0;
   for(int i=0;i<=n;i++)
     for(int j=0;j<=r;j++)
       f[i][j]=0;
   for(int i=1;i<=n;i++)
   {
     hm[i][0]=1;
     for(int j=1;j<=r;j++)
       hm[i][j]=hm[i][j-1]*(double)(1-p[i]);
   }
}
int main()
{
  //freopen("4008.in","r",stdin);
  //freopen("4008.out","w",stdout);
  scanf("%d",&T);
  while(T--)
  {
    scanf("%d%d",&n,&r);
    for(int i=1;i<=n;i++)
      scanf("%lf%d",&p[i],&d[i]);
    init();
    f[0][r]=1;
    for(int i=0;i<n;i++) 
      for(int j=r;j>=1;j--)
      {
        f[i+1][j]+=f[i][j]*hm[i+1][j];
        if(j-1>=0)
        {
          ans+=(double)(1-hm[i+1][j])*f[i][j]*d[i+1];
          f[i+1][j-1]+=(double)(1-hm[i+1][j])*f[i][j];
        }
      }
    printf("%.10lf\n",ans);
  }
  return 0;
}

bzoj 3036: 绿豆蛙的归宿
因为是有向无环图,所以我们可以直接在上面做DP.F[i]表示到达i的期望步数,我把这题的图反过来建,反着DP,事实上正过来也一样。我还多开了一个数组h记录到某个点的概率,但因为f数组本身存的就是期望,这个数组完全可以省去.

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
using namespace std;
const int maxn=100000+10;
int d[maxn],n,m,vis[maxn];
double f[maxn],h[maxn];
struct node
{
  int to,cost;
};
vector<node> g[maxn];
void dfs(int p)
{
  if(vis[p]) return;
  vis[p]=1;
  for(int i=0;i<g[p].size();i++)
  {
    node e=g[p][i];int v=e.to;dfs(v);
    f[p]=f[p]+(double)f[v]/(double)d[v]+e.cost*(double)h[v]/(double)d[v];
    h[p]+=h[v]/d[v];
  }
}
int main()
{
  //freopen("3036.in","r",stdin);
  //freopen("3036.out","w",stdout);
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++)
  {
    int x,y,c;
    scanf("%d%d%d",&x,&y,&c);
    g[y].push_back((node){x,c});
    d[x]++;
  }
  h[1]=1.000;dfs(n);
  printf("%.2lf\n",f[n]);
  return 0;
}

bzoj 3450
虽然对于?的字符是存在概率上的问题,但我们可以像正常的DP一样把它做成期望值。F[I]表示到第i个字符获得的期望分数,d[i]表示到第i个字符的期望连击数,转移只要分情况讨论下。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=300000+10;
char s[maxn];
int n;
double f[maxn],d[maxn];
int main()
{
  //freopen("3450.in","r",stdin);
  //freopen("3450.out","w",stdout);
  scanf("%d%s",&n,&s);
  for(int i=1;i<=n;i++)
  {
    if(s[i-1]=='o')
    {
      f[i]=f[i-1]+(double)d[i-1]*2+(double)1;
      d[i]=d[i-1]+1;
    }
    if(s[i-1]=='x')
    {
      f[i]=f[i-1];
      d[i]=0;
    }
    if(s[i-1]=='?')
    {
      f[i]=f[i-1]+d[i-1]+(double)0.5;
      d[i]=d[i-1]/(double)2+(double)0.5;
    }
  }
  printf("%.4lf\n",f[n]);
  return 0;
}

bzoj 1426
这题切入的地方在于邮票之间并没有什么不同,可以说是”一视同仁“,设状态F[i]表示已经收集了i张邮票后还需要收集的期望邮票数,g[i]表示已经收集了i张邮票后还需要花费的期望钱数,我们可以倒着转移.f[i]=i/n*f[i]+(n-i)/n*f[i+1].处理g的时候有个技巧,就是只考虑它对以后会增加的钱数(把它当成是1).g[i]=(i/n*g[i](f[i]+1))+((n-i)/n*g[i+1](f[i+1]+1)).

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=10000+10;
double f[maxn],g[maxn];
int n;
int main()
{
  //freopen("1426.in","r",stdin);
  //freopen("1426.out","w",stdout);
  scanf("%d",&n);
  g[n]=f[n]=0;
  for(int i=n-1;i>=0;i--)
    f[i]=f[i+1]+(double)n/(double)(n-i);
  for(int i=n-1;i>=0;i--)
    g[i]=(double)i/(double)(n-i)*f[i]+f[i+1]+g[i+1]+(double)n/(double)(n-i);
  printf("%.2lf\n",g[0]);
  return 0;
}

bzoj 1076
这题会让人很有想法,但要AC并不容易.首先N非常小,而且宝物互相之间会有关系,所以将它们状压起来f[i][j]表示到第i轮宝物集合为j的最好期望得分,然后枚举出现的宝物转移到下一轮,但这样存在一个问题,在转移的过程中我们会让一些本来无法到达的状态变成可以到达,并因此付出一些代价(吃负的宝物),但我们无法甄别这些新的状态对未来的影响如何(可能会亏也可能赚),这样动规并不具有全局最优性,有个很好的办法解决,就是反过来推,从最后的状态往前推,如果中间推出了无效的状态,那么它就不会到达开始的状态(子集为空),这个思路的巧妙就在于我们不知道结束的状态,但我们知道开始的状态,所以默认所有状态都是有效的,因为无效就累计不到答案中。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=(1<<15)+10;
double f[101][maxn];
int k,n,p[20],pre[20];
int main()
{
  //freopen("bonus.in","r",stdin);
  //freopen("bonus.out","w",stdout);
  scanf("%d%d",&k,&n);
  for(int i=1;i<=n;i++)
  {
    scanf("%d",&p[i]);
    int x;scanf("%d",&x);
    while(x!=0)
    {
      pre[i]+=1<<(x-1);
      scanf("%d",&x);
    }
  }
  for(int i=k-1;i>=0;i--)
    for(int j=0;j<(1<<n);j++)
    {
      for(int l=1;l<=n;l++)
        {
          if(pre[l]==0||(j&pre[l])==pre[l])
          {
            int nx=j|(1<<(l-1));
            f[i][j]+=max(f[i+1][j],f[i+1][nx]+p[l]);
          }
          else f[i][j]+=f[i+1][j];
        }
      f[i][j]/=(double)n;
    }
  printf("%.6lf\n",f[0][0]);
  return 0;
}

bzoj 1415
这题和3036有点像,我们只要预先处理出可可到哪个点后聪聪会去的点就可以了,因为不是DAG,所以我们要要用记忆化搜索,同时如果两人一步之内可达(聪聪先走)要特判。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
using namespace std;
const int maxn=1000+10;
int dis[maxn][maxn],nxt[maxn][maxn],n,m,d[maxn],s,t,vis[maxn][maxn];
vector<int> g[maxn];
double f[maxn][maxn];
double dp(int s,int t)
{
  //cout<<s<<' '<<t<<endl;
  if(s==t) return 0;
  if(nxt[s][t]==t||nxt[nxt[s][t]][t]==t) return 1;
  if(f[s][t]) return f[s][t];
  f[s][t]+=1+dp(nxt[nxt[s][t]][t],t);
  for(int i=0;i<g[t].size();i++)
  {
    int v=g[t][i];
    f[s][t]+=1+dp(nxt[nxt[s][t]][t],v);
  }
  f[s][t]/=(double)d[t]+1;
  return f[s][t];
}
void bfs(int p)
{
  dis[p][p]=0;queue<int> Q;Q.push(p);vis[p][p]=1;
  while(!Q.empty())
  {
    int x=Q.front();Q.pop();
    for(int i=0;i<g[x].size();i++)
    {
      int v=g[x][i];
      if(vis[p][v]) continue;
      vis[p][v]=1;Q.push(v);dis[p][v]=dis[p][x]+1;
    }
  }
}
int main()
{
  //freopen("1415.in","r",stdin);
  //freopen("1415.out","w",stdout);
  scanf("%d%d%d%d",&n,&m,&s,&t);
  for(int i=1;i<=m;i++)
  {
    int x,y;scanf("%d%d",&x,&y);
    d[x]++;d[y]++;g[x].push_back(y);g[y].push_back(x);
  }
  for(int i=1;i<=n;i++) bfs(i);
  for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
      for(int k=0;k<g[i].size();k++)
      {
        int v=g[i][k];
        if(!nxt[i][j]) nxt[i][j]=v;
        else if(dis[v][j]<dis[nxt[i][j]][j]||(dis[v][j]==dis[nxt[i][j]][j]&&v<nxt[i][j])) nxt[i][j]=v;
      }
  //cout<<nxt[1][4]<<endl;
  //cout<<"OK"<<endl;
  printf("%.3lf\n",dp(s,t));
  return 0;
}

bzoj 3270
这题就不能用动态规划处理了,因为这两人可以随便乱转,步数是无穷的,我们借助高斯消元来解决,根据图的关系列出方程就可以直接解了。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
const int maxn=900+10;
const double eps=1e-7;
double a[maxn][maxn],p[maxn];
vector<int> g[maxn];
int n,d[maxn],m,s,t,N;
int id(int x,int y)
{
  return (x-1)*n+y;
}
void work(int x,int y)
{
  a[id(x,y)][id(x,y)]--;
  for(int i=0;i<g[x].size();i++)
    for(int j=0;j<g[y].size();j++)
    {
      int u=g[x][i],v=g[y][j],h1=id(x,y),h2=id(u,v);
      if(u==v) continue;
      if(u==x&&v==y) a[h1][h2]+=p[u]*p[v];
      if(u==x&&v!=y) a[h1][h2]+=p[u]*(double)(1-p[v])/(double)d[v];
      if(u!=x&&v==y) a[h1][h2]+=(double)(1-p[u])/(double)d[u]*p[v];
      if(u!=x&&v!=y) a[h1][h2]+=(double)(1-p[u])*(double)(1-p[v])/d[u]/d[v];
    }
}
void gauss()
{
  for(int i=1;i<=N;i++)
  {
    int j=i;
    while(!a[j][i]&&j<=N) j++;
    if(j==N+1) continue;
    if(j!=i) for(int k=1;k<=N+1;k++) swap(a[j][k],a[i][k]);
    for(int k=1;k<=N;k++)
    {
      if(k==i) continue;
      double t=a[k][i]/a[i][i];
      for(int l=1;l<=N+1;l++) a[k][l]-=t*a[i][l];
    }
  }
}
int main()
{
  //freopen("3270.in","r",stdin);
  //freopen("3270.out","w",stdout);
  scanf("%d%d%d%d",&n,&m,&s,&t);N=n*n;
  a[id(s,t)][N+1]=-1;
  for(int i=1;i<=m;i++)
  {
    int x,y;scanf("%d%d",&x,&y);
    g[x].push_back(y);g[y].push_back(x);
    d[x]++;d[y]++;
  }
  for(int i=1;i<=n;i++) scanf("%lf",&p[i]),g[i].push_back(i);
  for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
      work(i,j);
  gauss();
  /*for(int i=1;i<=N;i++)
  {
    for(int j=i+1;j<=N;j++) a[i][N+1]-=a[j][N+1]*a[i][j];
    a[i][N+1]/=a[i][i];
  }*/
  for(int i=1;i<=n;i++)
  {
    int t=id(i,i);
    if(i<n) printf("%.6lf ",a[t][N+1]/a[t][t]);
    else printf("%.6lf",a[t][N+1]/a[t][t]);
  }
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值