序:感觉概率什么的相比于其他的dp还是较为容易的 几乎都是线性的
解决概率问题的关键在于利用独立性对概率进行处理(乘乘加加)
A:
显然概率不能作为下标,那么就把钱数作为下标(由于总钱数<=10000)
然后贪心地取最小被抓概率
状态转移方程为
dp[i]=min(dp[i],dp[i−V[i]]∗P[i])
P[i]为被抓概率,V[i]为收益
B:
这题要注意HP1和HP2要反着读。。。
预处理出A胜利(P1)和B胜利(P2)的概率
那么对于整个战局(忽略平局情况)A的胜率为Pa=P1/(P1+P2),B的胜率为Pb=P2/(P1+P2)
由于如果开两维内存会炸,所以要滚动数组
转移方程为
dp[cur][i]=dp[cur][i−1]∗Pa+dp[cur1][i]∗Pb
;
//第一维为A的扣血量,第二维为B的扣血量
C:
这题有一个玄学贪心:根据Qi/Pi的值先从大到小排
然后就找到了状态转移的顺序:
ans+=dp[i][j−1]∗Q[j];
dp[i][j]+=dp[i−1][j−1]∗P[j]+dp[i][j−1]∗(1−P[j]−Q[j])
第一维是消耗钱数,第二维是到第几扇门
D:
网上什么kmp,ac自动机…
其实暴力解就好了
这题最关键的思想是:虽然一串字符可以表示指定字符串s的多种状态[1,4] [1,8]…
但我们只用知道能表示的最长长度就好了
预处理表示到s的第几位时加上某个字符能表示的最长位数,记为A[i][j]
题目求包含s的概率,但我们并不方便直接求,可以通过计算不包含s的概率
即敲到第m位所表示s的长度不超过len
状态转移方程为
dp[i][A[j][k]]+=dp[i−1][j]∗P[k]
P[k]表示敲击k字母的概率
E:
这是这道题中为数不多不是线性的题目
显然是一个树形dp
在这题中,要能想到把之前的子树的信息合并,再与当前子树信息合并
我们只用记录到x点最长长度为l时概率,记为dp[x][l]
t为 1/(1+l)
void dfs(int x,int f){
int cnt=0;
for(int h=0;h<(int)edge[x].size();h++){
int y=edge[x][h];
if(y==f)continue;
cnt++;
dfs(y,x);
if(cnt==1){
for(int i=0;i<=S;i++)
for(int j=0;j<=l;j++)
if(i+j<=S)dp[x][i+j]+=dp[y][i]*t;
continue;
}
for(int i=0;i<=S;i++)
for(int k=0;k<=S;k++)
for(int j=0;j<=l;j++)
if(k+j+i<=S)dp1[x][max(k+j,i)]+=dp[x][i]*dp[y][k]*t;
for(int i=0;i<=S;i++)dp[x][i]=dp1[x][i],dp1[x][i]=0;
}
if(cnt==0)dp[x][0]=1;
}
F:
不知道这里为什么会出现期望题
首先肯定要记录最远距离是什么
但我们可能会疑惑,万一定最右点为x,但整个过程中并走不到怎么办
所以可以用拓展的思想:dp[i][j][k]表示第i步到j这个点最远点为k且已到过k点
状态转移方程为:
dp[i][j][k]=dp[i-1][j][k]*(1-Pl-Pr)+dp[i-1][j][k-1]*Pr+dp[i-1][j][k+1]*Pl
[j< k]
dp[i][j][k]=dp[i-1][j][k]*(1-Pl-Pr)+dp[i-1][j-1][k-1]*Pr+dp[i-1][j-1][k]*Pr
初始化为dp[1][100][100]=1; 设100为起点
G:
这道题听说只能玄学卡常。。。
H:
这题的dp定义是当前自己的队伍种类对应的最大获胜概率
状态转移方程为:
dp[cur][i]=max(dp[cur][i],dp[cur^1][j]*P[j][i])
dp[cur][j]=max(dp[cur][j],dp[cur^1][j]*P[j][i])
I:
其实这题只要模拟就好了
类似于F题的dp状态定义,什么时刻,在哪个点,不经过哪个点
J:
又是一道令人作呕的博弈题
可以知道:
庄家去完之后,这一回合就结束了
所以可以直接得出庄家取完后的胜负情况
定义f2[i][j]为庄家取时我方有i张牌,对方有j张牌时的胜率
那么可以得出,当i<=j时我方是必输的,把f2[i][j]赋值为1
那么可以得到对于f2的状态转移方程为f2[i][j]=max(f2[i][j],f2[i][j+k]*/(double)13)[因为庄家足够聪明]
对于我方,有两种决策:1.抽牌,2.放弃抽牌,让庄家抽牌
对于第二种情况,胜率即为1-f2[i][j]
对于第一种情况,类似于f2的处理f1[i][j]=max(f1[i][j],f[i][j+k]*/(double)13)
代码实现:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define du double
du f2[50][50],f1[50][50];
void chk(){
for(int i=21;i>=2;i--){
for(int j=21;j>=2;j--){
if(i<=j){
f2[i][j]=1;
continue;
}
for(int k=1;k<=13;k++){
int a=k;
if(k>=10)a=10;
f2[i][j]+=f2[i][j+a]/(du)13;
}
}
}
}
void chk1(){
for(int i=21;i>=2;i--){
for(int j=21;j>=2;j--){
for(int k=1;k<=13;k++){
int a=k;
if(k>=10)a=10;
f1[i][j]+=f1[i+a][j]/(du)13;
}
f1[i][j]=max(f1[i][j],1-f2[i][j]);
}
}
}
int main(){
chk();
chk1();
int T;
scanf("%d",&T);
while(T--){
char A[5];
scanf("%s",A);
int a=0,b=0;
for(int i=0;i<2;i++){
if(A[i]=='T'||A[i]=='J'||A[i]=='K'||A[i]=='Q')a+=10;
else if(A[i]=='A')a+=1;
else a+=A[i]-'0';
}
for(int i=2;i<4;i++){
if(A[i]=='T'||A[i]=='J'||A[i]=='K'||A[i]=='Q')b+=10;
else if(A[i]=='A')b+=1;
else b+=A[i]-'0';
}
printf("%lf\n",f1[a][b]);
if(f1[a][b]>0.5)puts("YES");
else puts("NO");
}
return 0;
}
K:
可以yy出游戏卡牌的排列就是
(n+m)!
看到n+m<=20,显然就是状压dp
当当前卡牌可以杀死敌人时,获胜的方案即为得到该状态的方案数*
(n+m−i−j)!
状态转移方程即为
dp[i|(1<<k)]+=dp[i]
取第k张牌
int main(){
int T;
scanf("%d",&T);
fact[0]=1;
for(int i=1;i<=20;i++)fact[i]=fact[i-1]*i;
while(T--){
memset(dp,0,sizeof(dp));
int p,n,m;
scanf("%d%d%d",&p,&n,&m);
int tta=n+m;
for(int i=n;i<tta;i++)scanf("%d",&A[i]);
long long cnt=0;
dp[0]=1;
int t=(1<<tta)-1;
for(int i=0;i<=t;i++){
if(!dp[i])continue;
int cnt1=0,cnt2=0;
int s=0;
for(int k=0;k<tta;k++){
if(i&(1<<k)){
if(k<n)cnt1++;
else cnt2++,s+=A[k];
}
}
if(s>=p){
cnt+=dp[i]*fact[tta-cnt1-cnt2];
continue;
}
if(cnt1-cnt2+1<=0)continue;
for(int k=0;k<tta;k++){
if(i&(1<<k))continue;
dp[i|(1<<k)]+=dp[i];
}
}
long long g=gcd(cnt,fact[tta]);
printf("%lld/%lld\n",cnt/g,fact[tta]/g);
}
return 0;
}