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;
}