[ABC470E]Concentration

Posted on 2026-09-22 20:35  暮光落阳  阅读(5)  评论(0)    收藏  举报

[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。: (