题解
对于
n
n
n较小的情况进行动态规划。
令
f
[
i
]
[
j
]
f[i][j]
f[i][j]为当前有i张重掷符卡,当前骰子点数为j,可能的最大期望伤害。如果使用了重掷符卡,那么从
1
+
1
6
∑
k
=
1
6
f
[
i
−
1
]
[
k
]
1+\frac {1}{6}\sum^{6}_{k=1}f[i-1][k]
1+61∑k=16f[i−1][k]转移,否则丢弃这张符卡从
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j]转移。
打表可以发现对于充分大的
i
i
i,
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
+
1
f[i][j]=f[i-1][j]+1
f[i][j]=f[i−1][j]+1。即有很多张重掷符卡的时候一定会尽量用这些重掷符卡直到张数减小到一定程度,因此可以对这个一定程度设定一个阀值
S
S
S,计算出
f
[
1
⋯
S
]
[
j
]
f[1\cdots S][j]
f[1⋯S][j],对于阀值之内的数输出
f
[
n
]
[
u
]
f[n][u]
f[n][u],否则输出
f
[
S
]
[
u
]
+
n
−
S
f[S][u]+n-S
f[S][u]+n−S。
考虑证明:
假设当前
f
[
i
]
=
{
a
,
b
,
c
,
d
,
e
,
f
}
f[i]=\{a,b,c,d,e,f\}
f[i]={a,b,c,d,e,f},显然有
a
≤
b
≤
c
≤
d
≤
e
≤
f
a\le b\le c\le d\le e\le f
a≤b≤c≤d≤e≤f,那么可以假设当前
a
=
b
=
c
=
d
=
e
<
<
f
a=b=c=d=e<<f
a=b=c=d=e<<f,则重掷后的贡献就是
1
+
1
6
(
5
e
+
f
)
=
1
+
f
−
5
6
(
f
−
e
)
1+\frac{1}{6}(5e+f)=1+f-\frac{5}{6}(f-e)
1+61(5e+f)=1+f−65(f−e)。
因此重掷之后
f
′
−
e
′
=
f
−
(
1
+
f
−
5
6
(
f
−
e
)
)
=
5
6
(
f
−
e
)
−
1
f'-e'=f-(1+f-\frac{5}{6}(f-e))=\frac{5}{6}(f-e)-1
f′−e′=f−(1+f−65(f−e))=65(f−e)−1,即每次重掷都会使差减小
1
6
\frac{1}{6}
61,因此
O
(
l
o
g
)
O(log)
O(log)轮后差就会缩小到
≤
1
\le1
≤1,之后答案会一直由重掷贡献。
关于阀值设定?
设定一个阀值这个思想在实数域上二分里也经常用到,不过我当时没打表打出来它应该设成
100
100
100
后来打了个表发现
50
50
50就可以了???然后交了一发只有
60
p
t
s
60pts
60pts???然后设成
1000
1000
1000也能过???看来以后在时间空间允许的时候还是把阀值开大点保险啊,实在不知道阀值也可以把阀值设大点这么瞎搞搞说不定就是正解??? 整挺好
以后遇到这种乍一看看不出明显结论的要学会针对小数据打表处理找规律
f
i
n
e
fine
fine我是没看出来怎么打的表,然后发现就是设计出了
D
P
DP
DP方程之后输出了
D
P
DP
DP结果看的结论,还是
D
P
DP
DP不熟啊
代码
因为有对于 f f f的预处理,后面又是直接取的 m a x max max,最后又调用的是 f [ S ] [ u ] f[S][u] f[S][u],所以相当于已经取了 f [ i ] [ u ] = m a x ( u ∗ k , ∑ j f [ i − 1 ] [ j ] 6 + 1 ) f[i][u]=max(u*k,\frac{\sum_{j}f[i-1][j]}{6}+1) f[i][u]=max(u∗k,6∑jf[i−1][j]+1)
#include <bits/stdc++.h>
using namespace std;
const int S=100;
int T,n,u,k;
double f[S+1][7];
inline int read(){
int cnt=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-f;c=getchar();}
while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
return cnt*f;
}
inline void sol(){
n=read(),u=read(),k=read();
memset(f,0,sizeof(f));
for(int i=1;i<=6;++i) f[0][i]=k*i;
for(int i=1;i<=S;++i){
double p=0;
for(int w=1;w<=6;++w) p+=f[i-1][w];
p/=6;
for(int w=1;w<=6;++w) f[i][w]=max(f[i-1][w],p+1);
}
double ans;
if(n<=S) ans=f[n][u];
else ans=f[S][u]+n-S;
printf("%.10lf\n",ans);
}
signed main(){
freopen("roll.in","r",stdin);
freopen("roll.out","w",stdout);
T=read();
while(T--) sol();
return 0;
}