[ABC470E]Concentration
毫无疑问,这是一道期望dp。
这道题很朴素,只需要设计状态,之后记忆化搜索或递推,于是就做完了。
不过理解题意还是需要时间的,简而言之,
有 \(N\) 对( \(2N\) 张)牌,一次选 \(2\) 张:
-
若是同一对则将这一对的权值加入得分,并移除这两张,
-
否则,\(生命值\)
--;
直至此类操作后 \(生命值=0\) 或 \(剩余牌数=0\) 。
求在最优策略下的得分期望。
--Warning!--
他会在选择每一张牌后永远记住此牌上的值。考虑答案的统计,由于最终答案仅与选中的元素和有关,而每个元素被选中的可能是相等的,那么可以忽视元素顺序,仅维护 \(加入得分的牌对数\),答案即是 \({加入得分的牌对数\over \text{总牌对数(n)}}\times 元素和\) 。
贪心的,除非操作后 \(生命值=0\) ,否则对于已知的成对的牌,立刻选中并移除不会使答案更劣。于是设计状态时只需视为已知的牌中仅存在单张的牌。
需要维护的信息只有 生命值,加入得分的牌对数,剩余牌数。
建立一个三维数组,递推的话先初始化终止状态,因为转移较复杂,建议填表转移,只需保证合法的状态仅从合法状态转移,且合法的终止状态均被初始化,就无须处理非法状态了。
转移见代码。
Code
有剪枝。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int i;
scanf("%lld",&i);
return i;
}
signed main(){
//freopen("test.in","r",stdin);
//freopen("test.out","w",stdout);
int n=read(),hp=read(),exp=0;
for(int i=1;i<=n;i++)exp+=read();
double a[210][210][410];//cshp,pr,re
for(int j=1;j<=n;j++){
for(int re=2*(n-j);re>=0;re--){
a[0][j][re]=1.*exp*j/n;
}
}
for(int ak=1;ak<=hp;ak++)a[ak][n][0]=1.*exp;
for(int j=n-1;j>=0;j--){
for(int re=n-j;re<=2*(n-j);re++){
for(int ak=1;ak<=hp-(n-j-re/2);ak++){
int di=2*n-re-2*j,co=re-di;
a[ak][j][re]=1.*di/re*a[ak][j+1][re-1]+
1.*co/re*(re>1?( 1.*di/(re-1)*a[ak-1][j+bool(ak>1)][re-2]
+ 1.*(co-2)/(re-1)*a[ak-1][j][re-2]
+ 1./(re-1)*a[ak][j+1][re-2] ): 0);
}
}
}
printf("%lf",a[hp][0][2*n]);
return 0;
}
循环顺序是为了方便剪枝,不剪枝需要跳出di<0,co<0的非法情况。
记忆化搜索应该会更直接更快更简单。
其实我做的时候没发现他是一张一张翻开的,对着更复杂的转移调了3h。: (
$我们深知自己的平凡,而行,于此途中$
浙公网安备 33010602011771号