Luogu P2473 [SCOI2008]奖励关

时间:2024-08-30 17:07:32

比较恶心的概率(期望)+状压DP,想正推2H的我瑟瑟发抖

由于数据范围不大,因此我们可以直接状压每个宝物取或不取的情况,设\(f_{i,j}\)表示前\(i\)轮且宝物是否取过的状态为\(j\)时的方案总数,但是我们发现这样可能会导致一些不合法的状态也得到转移,因此我们考虑倒推

用\(f_{i,j}\)表示表示在第\(1\)轮到第\(i-1\)轮内宝物是否取过的状态为\(j\),第\(i\)轮到第\(k\)轮的最大期望得分,那么这样就可以通过倒推进行转移了。

具体转移的时候我们枚举所有的宝物限制,那么转移就很明显了

不过由于这里要求的是期望值,而每一次需要除以\(n\),最后的\(f_{1,0}\)即为答案

CODE

#include<cstdio>
#include<cctype>
using namespace std;
typedef double DB;
const int N=16,INF=-1e9;
int n,p[N],m,s[N],x,tot;
DB f[105][(1<<N)+5];
inline char tc(void)
{
static char fl[100000],*A=fl,*B=fl;
return A==B&&(B=(A=fl)+fread(fl,1,100000,stdin),A==B)?EOF:*A++;
}
inline void read(int &x)
{
x=0; char ch; int flag=1; while (!isdigit(ch=tc())) flag=ch^'-'?1:-1;
while (x=(x<<3)+(x<<1)+ch-'0',isdigit(ch=tc())); x*=flag;
}
inline int calc(int x)
{
int res=0; while (x) res+=x&1,x>>=1; return res;
}
inline DB max(DB a,DB b)
{
return a>b?a:b;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
register int i,j,k; read(m); read(n); tot=(1<<n)-1;
for (i=0;i<n;++i)
{
read(p[i]); read(x);
while (x) s[i]|=(1<<x-1),read(x);
}
for (i=m;i>=1;--i)
for (j=0;j<=tot;++j)
{
for (k=0;k<n;++k)
if ((s[k]&j)==s[k]) f[i][j]+=max(f[i+1][j],f[i+1][j|(1<<k)]+p[k]); else f[i][j]+=f[i+1][j];
f[i][j]=(DB)f[i][j]/n;
}
return printf("%.6lf",f[1][0]),0;
}