I-Increasing Subsequence

fi,j,0/1f_{i,j,0/1}fi,j,0/1​表示上一轮第一个人选了iii,第二个人选了jjj,并且当前是第1/2个人选择的概率

转移考虑枚举k下一步往哪走
fi,k,1=∑fi,j,0/cntf_{i,k,1}=\sum f_{i,j,0}/ \text{cnt}fi,k,1​=∑fi,j,0​/cnt

fk,j,0=∑fi,j,1/cntf_{k,j,0}=\sum f_{i,j,1}/ \text{cnt}fk,j,0​=∑fi,j,1​/cnt

答案是所有数组之和:每进一个新状态都意味着游戏多进行了一局,因此把出现的概率全部累加就是期望局数。
显然的暴力~
时间复杂度:O(n3)O(n^3)O(n3)

Code1

#include<bits/stdc++.h>using namespace std;
using ll=long long;const int N=5010;
const int mod=998244353;ll f[N][N][2];
ll inv[N];
int a[N],n;
ll qmi(ll a,ll b)
{ll res=1;while(b){if(b&1) res=res*a%mod;a=a*a%mod;b>>=1;}return res;
}
int main()
{ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);cin>>n;for(int i=1;i<=n;i++) inv[i]=qmi(i,mod-2);for(int i=1;i<=n;i++) cin>>a[i];for(int i=1;i<=n;i++)for(int j=0;j<=n;j++){if(j==0) f[i][j][0]=inv[n];int ct=0;// 第一个人for(int k=j+1;k<=n;k++)if(a[k]>a[i]&&a[k]>a[j]) ct++;for(int k=j+1;k<=n;k++)if(a[k]>a[i]&&a[k]>a[j]) f[i][k][1]=(f[i][k][1]+inv[ct]*f[i][j][0])%mod;// 第二个人ct=0;for(int k=i+1;k<=n;k++)if(a[k]>a[i]&&a[k]>a[j]) ct++;for(int k=i+1;k<=n;k++)if(a[k]>a[i]&&a[k]>a[j]) f[k][j][0]=(f[k][j][0]+inv[ct]*f[i][j][1])%mod;}ll ans=0;for(int i=1;i<=n;i++)for(int j=0;j<=n;j++) ans+=f[i][j][0]+f[i][j][1],ans%=mod;cout<<ans<<'\n';return 0;
}

没听懂讲题人上述解法的优化,询问了mrk大佬的思路。并且参考下面题解,感觉更容易理解lalalzo题解

对于上述做法关键是我们需要区分这一步是谁走的,而下面的做法考虑在所给序列的值域上从小到大走,保证了所选必须比前面所有选的值要大,还有一个限制就是对于每一个人的所选序号单增。

设计dp

状态表示:fu,vf_{u,v}fu,v​表示最后一个人选择vvv而倒数第二个人选择uuu,值域从小到大走需要满足u<vu<vu<v停下来的期望步数。

状态转移:考虑移动一步fu,v→fv,kf_{u,v}\to f_{v,k}fu,v​→fv,k​需要满足u<v<kand posu<posku<v<k \text{ and } \text{pos}_u<\text{pos}_ku<v<k and posu​<posk​
fu,v=1cnt∑kfv,k+1f_{u,v}=\frac{1}{\text{cnt}}\sum_k f_{v,k}+1fu,v​=cnt1​k∑​fv,k​+1
发现满足posu<posk\text{pos}_u<\text{pos}_kposu​<posk​意味着每一个人的所选序号单增。非常巧妙啊~

于是有下面Code2常见的记忆化搜索写法(我之前比较熟悉记忆化搜索)

Code2

#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{T res=0;char ch=getchar();while(!isdigit(ch)) ch=getchar();while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();return res;
}
const int N=5010,mod=998244353;
using ll=long long;
int pos[N],n;
ll inv[N],f[N][N];
ll qmi(ll a,ll b)
{ll v=1;while(b){if(b&1) v=v*a%mod;a=a*a%mod;b>>=1;}return v;
}
// u<v<k
ll dfs(int u,int v)
{if(f[u][v]!=-1) return f[u][v];// f[u][v] -> f[v][k]  u<v<k&&pos[u]<pos[k]int ct=0;for(int k=v+1;k<=n;k++) if(pos[u]<pos[k]) ct++;f[u][v]=(ct>0?1:0);for(int k=v+1;k<=n;k++) if(pos[u]<pos[k]) f[u][v]=(f[u][v]+dfs(v,k)*inv[ct])%mod;return f[u][v];
}
int main()
{n=rd();for(int i=1;i<=n;i++) pos[rd()]=i;for(int i=1;i<=n;i++) inv[i]=qmi(i,mod-2);memset(f,-1,sizeof f);ll ans=0;for(int i=1;i<=n;i++) ans=(ans+dfs(0,i))%mod;ans=ans*inv[n]%mod;printf("%lld\n",ans);}

Code3

把上面记忆化搜索代码转化为迭代(考虑该状态会对哪些状态产生贡献),就有了下面的Code3

#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{T res=0;char ch=getchar();while(!isdigit(ch)) ch=getchar();while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();return res;
}
const int N=5010,mod=998244353;
using ll=long long;
int pos[N],n;
ll inv[N],f[N][N];
ll qmi(ll a,ll b)
{ll v=1;while(b){if(b&1) v=v*a%mod;a=a*a%mod;b>>=1;}return v;
}
int main()
{n=rd();for(int i=1;i<=n;i++) pos[rd()]=i;for(int i=1;i<=n;i++) inv[i]=qmi(i,mod-2);// f[k][i] -> f[i][j]  k<i<j && pos[k]<pos[j]// f[k][i] +=1/ct f[i][j] + 1for(int i=n;i>=1;i--){for(int k=0;k<i;k++){int ct=0;for(int j=i+1;j<=n;j++) if(pos[j]>pos[k]) ct++;for(int j=i+1;j<=n;j++)if(pos[j]>pos[k])f[k][i]=(f[k][i]+inv[ct]*f[i][j]%mod);if(ct) f[k][i]=(f[k][i]+1)%mod;}}ll ans=0;for(int i=1;i<=n;i++) ans=(ans+f[0][i])%mod;ans=ans*inv[n]%mod;printf("%lld\n",ans);}

Code4

不难发现每次我们想知道posk<posj\text{pos}_k<\text{pos}_jposk​<posj​,可以预处理前缀和优化。

#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{T res=0;char ch=getchar();while(!isdigit(ch)) ch=getchar();while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();return res;
}
const int N=5010,mod=998244353;
using ll=long long;
int pos[N],n;
ll sum[N],cnt[N],inv[N],f[N][N];
ll qmi(ll a,ll b)
{ll v=1;while(b){if(b&1) v=v*a%mod;a=a*a%mod;b>>=1;}return v;
}
int main()
{n=rd();for(int i=1;i<=n;i++) pos[rd()]=i;for(int i=1;i<=n;i++) inv[i]=qmi(i,mod-2);for(int i=n;i>=1;i--){memset(cnt,0,sizeof cnt);memset(sum,0,sizeof sum);for(int j=i+1;j<=n;j++){cnt[pos[j]]++;sum[pos[j]]=f[i][j];}for(int j=n-1;j>=0;j--){cnt[j]+=cnt[j+1];sum[j]=(sum[j]+sum[j+1])%mod;}for(int k=0;k<i;k++) {int id=pos[k];f[k][i]=(sum[id]*inv[cnt[id]]%mod+1)%mod;}}ll ans=0;for(int i=1;i<=n;i++) ans=(ans+f[0][i])%mod;ans=ans*inv[n]%mod;printf("%lld\n",ans);
}

总结:

其实对于上述两种方法有本质的区别就是定义的状态一个是概率一个是期望步数,需要注意如何定义状态~

要加油哦~

2021牛客暑期多校训练营1 I-Increasing Subsequence(期望dp+优化)相关推荐

  1. 2021牛客暑期多校训练营6 :D Gambling Monster 期望dp + fwt + cdq分治

    传送门 文章目录 题意: 思路: 题意: 给你一个大轮盘,被分成了nnn个区域0,1,2,..,n−10,1,2,..,n-10,1,2,..,n−1,每个区域被转到的概率是ai∑j=0n−1aj\f ...

  2. 2021牛客暑期多校训练营4 B - Sample Game 期望dp\生成函数

    传送门 文章目录 题意: 思路: 题意: 给你一个生成器,每次生成1−n1-n1−n其中的某个数的概率为pip_ipi​,生成的规则如下: (1)(1)(1)随机生成一个数加入集合. (2)(2)(2 ...

  3. 2021牛客暑期多校训练营5 E-Eert Esiwtib(树形dp+位运算)

    E-Eert Esiwtib 位运算考虑贡献时分0/1按位模拟考虑 fu,0/1/2f_{u,0/1/2}fu,0/1/2​表示子树u中点(包括u)到u所有路径的或/与/异或值. 转移的时候我们要考虑 ...

  4. 2021牛客暑期多校训练营4 B-Sample Game(概率DP)

    B-Sample Game ding_ning123大佬题解 注:上述题解图片来自ding_ning123大佬题解 Code #include<bits/stdc++.h> using n ...

  5. 2021牛客暑期多校训练营9

    2021牛客暑期多校训练营9 题号 题目 知识点 A A Math Challenge B Best Subgraph C Cells D Divide-and-conquer on Tree E E ...

  6. 2021牛客暑期多校训练营5

    2021牛客暑期多校训练营5 题号 题目 知识点 A Away from College B Boxes 概率 C Cheating and Stealing D Double Strings 线性d ...

  7. 2021牛客暑期多校训练营4

    2021牛客暑期多校训练营4 题号 题目 知识点 A Course B Sample Game C LCS D Rebuild Tree E Tree Xor 思维+线段树 F Just a joke ...

  8. 2021牛客暑期多校训练营3

    2021牛客暑期多校训练营3 题号 题目 知识点 A Guess and lies B Black and white C Minimum grid 二分图匹配 D Count E Math 数论+打 ...

  9. 2021牛客暑期多校训练营2

    2021牛客暑期多校训练营2 题号 题目 知识点 A Arithmetic Progression B Cannon C Draw Grids D Er Ba Game E Gas Station F ...

  10. 2021牛客暑期多校训练营1

    2021牛客暑期多校训练营1 题号 题目 知识点 难度 A Alice and Bob 博弈论 B Ball Dropping 计算几何 签到 C Cut the Tree D Determine t ...

最新文章

  1. [JUC-5]ConcurrentHashMap源码分析JDK8
  2. TCL withSNPS info existscreate_cellcreate_netconnect_net
  3. linux测试dvi接口,Pro Capture-DVI 2路高清DVI采集卡 支持Linux系统更专业
  4. 操作数据库(对战小游戏)
  5. P6620 [省选联考 2020 A 卷] 组合数问题(斯特林数、下降幂)
  6. LeetCode 206. 反转链表 思考分析
  7. zoj 1789 The Suspects
  8. 【深入浅出精华版视频】-刘意day13思维导图整理
  9. 百度地图-根据地址获取经纬度
  10. 使用N2N软件远程管理DLAP221设备
  11. 京瓷m1025维修模式进不去_京瓷m1025维修模式进不去_使用中,电脑突然蓝屏?千米电脑维修工程师告诉你原因!......
  12. python中英文字母和中文汉字所占的字节
  13. 初体验CSDN Chit GPT
  14. Webpack 实战入门系列(三):生产配置、样式文件分离及输出清理
  15. oracle 嵌套 哈希,Oracle-三种联接方法(哈希连接、嵌套连接、笛卡儿乘积)
  16. 盖茨、马斯克都遵循的终身学习法则:知识不是由学科划分的
  17. MTK IMS框架简析(2)——IMS注册过程
  18. JQuery放大镜效果实现实例
  19. AutoCAD Mechanical v2022.1.2 CAD机械版简体中文精简直装版
  20. 美国新闻集团拟起诉微软谷歌OpenAI;大厂核心技术人员开启创业潮;京东云首次发布数智平台“优加”丨每日大事件...

热门文章

  1. mac下nvm_Mac OS 使用 nvm 管理 node 与 npm 版本
  2. java实用教程——常用实用类——String类(字符串类)
  3. java实用教程——组件及事件处理——对话框(颜色对话框,自定义对话框)
  4. 唐山师范学院计算机论文,唐山师范学院校园网络解决方案 毕业论文
  5. [JavaWeb-MySQL]多表查询练习
  6. [蓝桥杯2019初赛]矩形切割-找规律
  7. 哈工大威海计算机组成原理,哈工大威海计算机组成原理复习.pdf
  8. 数据结构与算法--利用栈实现队列
  9. windows环境下ELK平台搭建
  10. word List44