news 2026/7/9 17:21:24

【AT_dp题单刷题记录】(2)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【AT_dp题单刷题记录】(2)

AT_dp_j Sushi

首先有一个显然但重要的性质,就是盘子之间是没有区别的,比如1号盘子有2个寿司,3号盘子有3个寿司,和1号盘子有3个寿司,3号盘子有2个寿司的期望次数是相同的

这说明我们无需考虑每一个盘子具体有多少寿司,只需要考虑每种寿司数量的盘子个数,就是有0个寿司的盘子总数,有1个寿司的盘子总数,有2个寿司的盘子总数和有3个寿司的盘子总数

由于n只有300,所以我们可以暴力一点,把所有信息都记录在dp数组里

假设dp[p][i][j][k]表示当前有p个盘子上有0个寿司,有i个盘子上有1个寿司,j个盘子上有2个寿司,k个盘子上有3个寿司,这样的情况下的期望次数(即答案)

但是这样设状态的话空间就爆了,由于p+i+j+k=n,所以我们可以压掉p这一维,用n-i-j-k替代,当然压掉其他维也是可以的。

即设dp[i][j][k]表示有i个盘子上有1个寿司,j个盘子上有2个寿司,k个盘子上有3个寿司,这样的情况下的期望次数

然后考虑状态转移方程:

如果选到了0个寿司的盘子的话,那么没有盘子的寿司数量发生变化,概率为(n-i-j-k)/n,所以这部分的期望次数是

如果选到了1个寿司的盘子的话,那么i少了1,也就是1个寿司的盘子数量少了1,这样的概率是i/n,所以这部分的期望次数是

如果选到了2个寿司的盘子的话,那么j少了1,i多了1,也就是2个寿司的盘子数量少了1,然后这个盘子变成1个寿司,所以一个寿司的盘子数量多了1,概率为j/n,这部分拿到期望次数是

如果选到了3个寿司的话,同理,这部分期望次数是

由于只会选到这4种情况,所以这种情况下的期望次数就是这四者的和+1,也就是说

左右两边都有dp[i][j][k],合并同类项变成可递推的形式:

然后注意转移顺序,因为(i,j,k)从(i,j+1,k-1)转移,既然要保证算(i,j,k)时(i,j+1,k-1)已计算出,所以必须先按照k从小到大循环。因为(i,j,k)也从(i+1,j-1,k)转移,所以第二层是j从小到大循环,所以枚举顺序是:k->j->i

最终答案就是最初情况下,假设1个寿司的盘子个数cnt1,2个寿司的盘子个数cnt2,3个寿司的盘子个数cnt3,那么答案是dp[cnt1][cnt2][cnt3]

代码如下:

#include<bits/stdc++.h> using namespace std; #define _for(i,a,b) for(int i = (a) ; i <= (b) ; i ++) #define for_(i,a,b) for(int i = (a) ; i >= (b) ; i --) const int maxn = 3e2 + 10; double dp[maxn][maxn][maxn]; int n; int a[maxn]; int cnt[maxn]; int main(){ ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin >> n; _for(i , 1 , n) { cin >> a[i]; cnt[a[i]] ++; } _for(k , 0 , n){ _for(j , 0 , n){ _for(i , 0 , n){ if(i + j + k == 0) continue; if(i) dp[i][j][k] += (i * dp[i - 1][j][k]) / (i + j + k); if(j) dp[i][j][k] += (j * dp[i + 1][j - 1][k]) / (i + j + k); if(k) dp[i][j][k] += (k * dp[i][j + 1][k - 1]) / (i + j + k); dp[i][j][k] += n * 1.0 / (i + j + k); } } } printf("%.9lf",dp[cnt[1]][cnt[2]][cnt[3]]); return 0; }

AT_dp_k Stones

这是一个明显的博弈论,假设dp[i]表示剩余i块石头时,当前操作者的胜负

注意是当前操作者,不一定是先手后手

那么显然,如果我能取到任意一种情况,让对手,也就是上一轮的操作者必败,那么我必胜

那么我们发现,如果存在dp[i-a[j]]=0,那么dp[i]=1

最终答案是dp[k],也就是一块石子都没动时的操作者,也就是先手胜负

代码如下:

#include<bits/stdc++.h> using namespace std; #define _for(i,a,b) for(int i = (a) ; i <= (b) ; i ++) #define for_(i,a,b) for(int i = (a) ; i >= (b) ; i --) const int maxm = 110; const int maxn = 1e5 + 10; int dp[maxn]; int n,m; int a[maxn]; int main(){ ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin >> n >> m; _for(i , 1 , n) cin >> a[i]; dp[0] = 0; _for(i , 1 , m) { _for(j , 1 , n){ if(i - a[j] >= 0 && dp[i - a[j]] == 0) { dp[i] = 1; break; } } } cout << (dp[m]?"First":"Second"); return 0; }

AT_dp_l Deque

这是我认为非常好的一个题,有两种做法

第一种是区间DP,首先考虑先手希望x-y大,后手希望x-y小,那么说明先手希望x尽可能大,因为和固定,x越大,y越小,同理,后手希望y尽可能大,也就是说两者都希望自己的得分尽可能多

那么假设dp[l][r]表示当前操作者在[l,r]取得的最大分数,由于这一次的操作者和上一次操作者相反,那么可以得出方程:

就是用总和减去对手的最小得分,就是自己的最大得分

正常来讲这道题到这里就结束了,也可以通过,但是这个dp的空间复杂度是可以优化的

因为一个区间的信息包括:左端点,右端点,区间长度,知其二可以求另一个,这里我们记录的信息是左右端点,但是转移是从小区间转移到大区间,所以在区间长度上存在顺序:从小到大

那么我们考虑把dp状态重新表示一下:

dp[len][l]表示以l为左端点,区间长度为len的当前操作者最大得分,所以方程可以改写成这样:

发现len永远从len-1转移,那么我们只需要每次枚举l之前把dp[len-1]的所有值都存在一个pre数组里,每次转移只需要在pre[l]和pre[l+1]取min

这样空间复杂度就优化到了O(n)

代码如下:

*#include<bits/stdc++.h> using namespace std; #define int long long #define _for(i,a,b) for(int i = (a) ; i <= (b) ; i ++) #define for_(i,a,b) for(int i = (a) ; i >= (b) ; i --) const int maxn = 3e3 + 10; int dp[maxn]; int pre[maxn]; int n,sum[maxn]; int a[maxn]; signed main(){ ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin >> n; _for(i , 1 , n) cin >> a[i]; _for(i , 1 , n) dp[i] = a[i],sum[i] = sum[i - 1] + a[i]; _for(len , 2 , n){ _for(l , 1 , n){ pre[l] = dp[l]; } for(int l = 1 ; l + len - 1 <= n ; l ++){ int r = l + len - 1; dp[l] = sum[r] - sum[l - 1] - min(pre[l],pre[l + 1]) ; } } cout << 2 * dp[1] - sum[n]; return 0; }

第二种做法:贪心

如果存在三个数满足“峰形”,即:

那么我们发现,这三个数的贡献和是一定的

假设轮到A选,他会选a[i]或a[i+2],那么轮到B时,他一定会选择a[i+1],然后A选a[i]和a[i+2]的另一个

所以这三个数对于A的贡献是a[i]+a[i+2]-a[i+1],所以三个数可以缩成一个数,同理,缩完之后的数还可以再缩,最终使得整个数组变成一个“谷形”,就是先递减后递增:因为不存在任何的峰,所以这就变成了一个贪心问题,两边选,哪个大选哪个

至于这个缩的过程,我们可以用一个栈模拟,每次检测栈顶的三个元素,如果满足峰形,就合并成一个元素

代码如下:

#include<bits/stdc++.h> using namespace std; #define int long long #define _for(i , a , b) for(int i = a ; i <= b ; i ++) #define for_(i , a , b) for(int i = a ; i >= b ; i --) const int maxn = 3e3 + 10; int a[maxn]; int n; int tp; bool check(){ if(tp < 3) return 0; if(a[tp] <= a[tp - 1] && a[tp - 2] <= a[tp - 1]) return 1; return 0; } signed main(){ cin >> n; _for(i , 1 , n) { int x; cin >> x; a[++ tp] = x; while(check()){ int x = a[tp]; -- tp; int y = a[tp]; -- tp; int z = a[tp]; -- tp; a[++ tp] = x + z - y; } } int lidx = 1,ridx = tp; int lans = 0,rans = 0; int vix = 0; while(lidx <= ridx){ int num; if(a[lidx] < a[ridx]) { num = a[ridx]; ridx --; } else{ num = a[lidx]; lidx ++; } if(!vix) lans += num; else rans += num; vix ^= 1; } cout << lans - rans; return 0; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/9 17:16:33

Juicebox完全指南:5步掌握Hi-C数据可视化与基因组三维结构分析

Juicebox完全指南&#xff1a;5步掌握Hi-C数据可视化与基因组三维结构分析 【免费下载链接】Juicebox Visualization and analysis software for Hi-C data - 项目地址: https://gitcode.com/gh_mirrors/ju/Juicebox 想要探索基因组的三维奥秘吗&#xff1f;Juicebox正…

作者头像 李华
网站建设 2026/7/9 17:13:58

MacBERT中文预训练模型深度解析与实战指南

MacBERT中文预训练模型深度解析与实战指南 【免费下载链接】MacBERT Revisiting Pre-trained Models for Chinese Natural Language Processing (MacBERT) 项目地址: https://gitcode.com/gh_mirrors/ma/MacBERT MacBERT是由哈工大讯飞联合实验室开发的创新型中文预训练…

作者头像 李华