拓扑排序+期望dp解决DAG上的期望

DAG上的期望DP与拓扑排序
本文探讨了在有向无环图(DAG)上进行期望动态规划(Expectation DP)的方法,通过逆向和正向建图分别解决从任意节点到达目标节点的期望步数和期望花费问题,利用拓扑排序进行高效求解。

例题1:luogu4316

倒推:

//luoguP4316.1 题意:n<1e5 m<2e5 DAG上到第N个点的期望步数 每次选择等概率前往下一个点,带边权
//一般期望倒着维护就不用乘贡献概率,正着就要乘贡献概率
//逆推版本:设dp[i]为i到点n的期望步数  
//dp[i]=sum_{}(dp[j]+eij)/ou(i)  //i->j  dp[n]=0,ans=dp[1]
//反向建图维护出dp即可
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=100005;
const int maxe=200005;
int cnt=0;
int head[maxn];int to[maxe];int nxt[maxe];int w[maxe];
void add(int u,int v,int ww)
{
	cnt++;nxt[cnt]=head[u];
	head[u]=cnt; to[cnt]=v;
    w[cnt]=ww;
}
double dp[maxn];
int in[maxn];//入度(出度反向建图变成入度)
int tp[maxn];//入度(不变)
queue<int>q;

int main(){
    int n,m;scanf("%d %d",&n,&m);
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++){
        int x,y,ww;scanf("%d %d %d",&y,&x,&ww);//反向建图跑
        in[y]++;add(x,y,ww);
        tp[y]++;
    }  
    for(int i=n;i>=1;i--){
        if(in[i]==0)q.push(i);
    }
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=nxt[i]){
            int v=to[i];int ww=w[i];
            dp[v]+=(double)(dp[u]+ww)/(double)tp[v];
            in[v]--;if(in[v]==0)q.push(v);
        }
    }
    for(int i=1;i<=n;i++)
        printf("%.2lf\n",dp[i]);

    system("pause");
    return 0;
}

正推:

//luoguP4316.2 DAG上到第N个点的期望步数
//一般期望倒着维护就不用乘贡献概率,正着就要乘贡献概率
//正推版本:设dp[i]为1到点i的期望步数
//dp[j]=sum(dp[i]+p[i]*eij)/out[i]; //i->j p[j]+=p[i]/out[i];
//正向建图即可
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=100005;
const int maxe=200005;
int cnt=0;
int head[maxn];int to[maxe];int nxt[maxe];int w[maxe];
void add(int u,int v,int ww)
{
	cnt++;nxt[cnt]=head[u];
	head[u]=cnt; to[cnt]=v;
    w[cnt]=ww;
}
double dp[maxn];
double p[maxn];//经过某点的概率
int in[maxn];//入度
int ou[maxn];//入度(不变)
queue<int>q;

int main(){
    int n,m;scanf("%d %d",&n,&m);
    memset(head,-1,sizeof(head));
    for(int i=1;i<=m;i++){
        int x,y,ww;scanf("%d %d %d",&x,&y,&ww);
        in[y]++;add(x,y,ww);
        ou[x]++;
    }  
    for(int i=1;i<=n;i++){
        if(in[i]==0)q.push(i);
    }
    p[1]=1.0;
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=nxt[i]){
            int v=to[i];int ww=w[i];
            dp[v]+=(double)(dp[u]+ww*p[u])/(double)ou[u];
            p[v]+=p[u]/ou[u];
            in[v]--;if(in[v]==0)q.push(v);
        }
    }
    //for(int i=1;i<=n;i++)
    printf("%.2lf\n",dp[n]);


    system("pause");
    return 0;
}

2019南京网络赛F.

对于g[i],转移的花费是dp[i],可以理解为接下来每一步都要多花dp[i]的花费

//2019南京网络赛D 期望DP+拓扑排序
//题意:DAG 起点1,终点n.每天可以等概率选择下一条路/留在原地  每次选择的花费是当前的天数,问到终点花费的期望
//思路:先维护出dp[i] i到n的期望天数 ,再维护出g[i]i到n的期望花费
//i->j dp[i]=sum{dp[j]+1   }/p + (dp[i]+1   )/p       p:i的选择个数
//i->j g[i] =sum{g[j]+dp[i]}/p + (dp[i]+g[i])/p       p:i的选择个数
//化简移项即可发现dp初始值为多少
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=100005;
const int maxe=200005;
int cnt=0;
int head[maxn];int to[maxe];int nxt[maxe];
void add(int u,int v)
{
	cnt++;nxt[cnt]=head[u];
	head[u]=cnt; to[cnt]=v;
}

int cnt2=0;
int head2[maxn];int to2[maxe];int nxt2[maxe];
void add2(int u,int v)
{
	cnt2++;nxt2[cnt2]=head2[u];
	head2[u]=cnt2; to2[cnt2]=v;
    
}

double dp[maxn];
int in[maxn];//入度(出度反向建图变成入度)
int tp[maxn];//入度(不变)

int in2[maxn];//入度(出度反向建图变成入度)
int tp2[maxn];//入度(不变)

double g[maxn];
queue<int>q;

int main(){
    int T;scanf("%d",&T);
    while(T--)
    {
    int n,m;scanf("%d %d",&n,&m);
    cnt=cnt2=0;
    memset(head,-1,sizeof(head));
    memset(head2,-1,sizeof(head2));
    memset(in,0,sizeof(in));
    memset(in2,0,sizeof(in2));
    memset(tp2,0,sizeof(tp2));
    memset(tp,0,sizeof(tp));
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d %d",&y,&x);//反向建图跑
        in[y]++;add(x,y);tp[y]++;
        //swap(x,y);
        in2[y]++;add2(x,y);tp2[y]++;

    }  
    while(!q.empty())q.pop();
    for(int i=n;i>=1;i--){
        dp[i]=0;
        if(in[i]==0)q.push(i);
        else{
            dp[i]=(double)(1+tp[i])/(double)tp[i];
        }
    }
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head[u];i!=-1;i=nxt[i]){
            int v=to[i];
            dp[v]+=(double)(dp[u])/(double)tp[v];
            //g[v]+=(double)(dp[u]+g[u]+tp[v]+1)/(double)(tp[v]);
            in[v]--;if(in[v]==0)q.push(v);
        }
    }

    while(!q.empty())q.pop();
    for(int i=n;i>=1;i--){
        g[i]=0;
        if(in2[i]==0)q.push(i);
        else{
            g[i]=dp[i]*(double)(1+tp2[i])/(double)tp2[i];
        }
    }
    //g[n]=dp[n];
    while(!q.empty()){
        int u=q.front();q.pop();
        for(int i=head2[u];i!=-1;i=nxt2[i]){
            int v=to2[i];
            //dp[v]+=(double)(dp[u]+1+tp[v])/(double)tp[v];
            g[v]+=(double)(g[u])/(double)(tp2[v]);
            in2[v]--;if(in2[v]==0)q.push(v);
        }
    }
    printf("%.2lf\n",g[1]);

    }
    system("pause");
    return 0;
}

 

如果你是一名专业的java高级架构师,现在你在面试知识,如下是 数据结构与算法的面试知识体系结构图 请按照这个体系给出每个知识点的学习理解方便记忆和消化,同时给出每个知识点的高频面试的标准面试答案,结合项目经验给出解释和实战 数据结构与算法面试核心知识体系 ├── 一、复杂度分析基础 │ ├── 时间复杂度 │ │ ├── 大O表示法:O(1), O(logn), O(n), O(nlogn), O(n²)等含义? │ │ ├── 最好、最坏、平均时间复杂度分析? │ │ └── 递归算法的时间复杂度分析:主定理? │ ├── 空间复杂度 │ │ ├── 算法运行所需的额外空间?原地操作含义? │ │ ├── 递归调用的空间复杂度(调用栈深度)? │ │ └── 如何权衡时间与空间复杂度? │ └── 实际应用 │ ├── 如何根据数据规模选择合适的算法?(10⁵数据不能用O(n²)) │ ├── 常数项优化在实际工程中的意义? │ └── 摊还分析:某些操作的平均代价? ├── 二、数组、链表与字符串 │ ├── 数组(Array) │ │ ├── 特点:随机访问O(1),插入删除O(n)? │ │ ├── 双指针技巧:快慢指针、左右指针、滑动窗口? │ │ ├── 缀和数组:快速计算区间和? │ │ └── 差分数组:快速进行区间增减操作? │ ├── 链表(Linked List) │ │ ├── 单链表、双链表、循环链表区别? │ │ ├── 虚拟头节点(dummy node)的作用? │ │ ├── 常见问题:反转链表、检测环、相交链表、合并有序链表? │ │ └── 链表排序:归并排序实现? │ └── 字符串(String) │ ├── 字符串匹配算法:KMP、Rabin-Karp? │ ├── 回文串问题:中心扩展法、动态规划? │ ├── 字符串操作:翻转、替换、分割? │ └── 不可变字符串的优势?(线程安全、缓存哈希值) ├── 三、栈、队列与哈希表 │ ├── 栈(Stack) │ │ ├── LIFO特性,应用场景:函数调用栈、括号匹配? │ │ ├── 单调栈:解决"下一个更大元素"问题? │ │ └── 最小栈:如何O(1)获取栈中最小值? │ ├── 队列(Queue) │ │ ├── FIFO特性,BFS算法基础? │ │ ├── 优先队列(堆):获取最值,Dijkstra算法? │ │ ├── 单调队列:解决滑动窗口最值问题? │ │ └── 双端队列(Deque):实现滑动窗口? │ └── 哈希表(Hash Table) │ ├── 原理:哈希函数、冲突解决(链地址法、开放寻址法)? │ ├── 设计哈希集合、哈希映射? │ ├── 实际应用:缓存(LRU)、快速查找、去重? │ └── 哈希碰撞攻击原理?如何设计好的哈希函数? ├── 四、树形数据结构 │ ├── 二叉树基础 │ │ ├── 遍历方式:序、中序、后序(递归/迭代)? │ │ ├── 二叉搜索树(BST):性质、查找、插入、删除? │ │ ├── 平衡二叉树:AVL树、红黑树基本概念? │ │ └── 完全二叉树、满二叉树定义? │ ├── 树的变种 │ │ ├── 堆(Heap):大顶堆、小顶堆,优先队列实现? │ │ ├── Trie树(缀树):字符串缀匹配? │ │ ├── 线段树:区间查询、区间更新? │ │ └── 树状数组(BIT):单点更新、缀查询? │ └── 树的应用 │ ├── 最近公共祖先(LCA)问题? │ ├── 二叉树的序列化与反序列化? │ ├── 树的直径、高度、路径和问题? │ └── 哈夫曼编码:数据压缩? ├── 五、图论算法 │ ├── 图的表示 │ │ ├── 邻接矩阵 vs 邻接表?空间时间复杂度? │ │ ├── 有向图、无向图、加权图? │ │ └── 图的遍历:BFS、DFS实现? │ ├── 最短路径算法 │ │ ├── Dijkstra算法:非负权图,贪心策略? │ │ ├── Bellman-Ford算法:处理负权边? │ │ ├── Floyd-Warshall算法:多源最短路径? │ │ └── A算法:启发式搜索? │ ├── 最小生成树 │ │ ├── Prim算法:从点开始扩展? │ │ ├── Kruskal算法:按边排序+并查集? │ │ └:适用场景:网络布线、电路设计? │ └── 其他图算法 │ ├── 拓扑排序:有向无环图(DAG),课程安排? │ ├── 并查集(Union-Find):连通分量,路径压缩优化? │ ├── 欧拉路径/回路:一笔画问题? │ └── 强连通分量:Kosaraju或Tarjan算法? ├── 六、排序与搜索算法 │ ├── 排序算法 │ │ ├── 比较排序:冒泡、选择、插入、归并、快速、堆排序? │ │ ├── 非比较排序:计数排序、桶排序、基数排序? │ │ ├── 稳定性分析:哪些是稳定排序? │ │ └── 各排序算法的时间/空间复杂度总结? │ ├── 搜索算法 │ │ ├── 二分查找:模板、边界处理、旋转数组搜索? │ │ ├── DFS深度优先:回溯法,排列组合问题? │ │ ├── BFS广度优先:最短路径,层级遍历? │ │ └── 启发式搜索:A算法,估价函数设计? │ └── 实际应用 │ ├── 海量数据排序:外部排序,归并思想? │ ├── 第K大/小元素:快速选择算法O(n)? │ ├── 在排序数组中查找目标值的起始和结束位置? │ └── 寻找旋转排序数组中的最小值? ├── 七、动态规划(DP) │ ├── 基本思想 │ │ ├── 重叠子问题、最优子结构? │ │ ├── 自顶向下(记忆化递归) vs 自底向上(迭代)? │ │ └── 状态定义、状态转移方程、边界条件? │ ├── 经典问题 │ │ ├── 背包问题:0-1背包、完全背包、状态压缩? │ │ ├── 最长公共子序列(LCS)、最长递增子序列(LIS)? │ │ ├── 编辑距离:字符串转换的最小操作数? │ │ ├── 股票买卖问题:多种限制条件? │ │ └── 打家劫舍、零钱兑换、路径问题? │ └── 解题技巧 │ ├── 如何识别DP问题?状态设计技巧? │ ├── 空间优化:滚动数组? │ ├── 输出具体方案而不仅仅是数值? │ └── 数位DP、状压DP、区间DP简介? ├── 八、贪心算法 │ ├── 基本概念 │ │ ├── 贪心选择性质?局部最优导致全局最优? │ │ ├── 与动态规划的区别?贪心无法回溯? │ │ └── 如何证明贪心策略的确性? │ ├── 典型问题 │ │ ├── 区间调度:最多不重叠区间? │ │ ├── 哈夫曼编码:最优缀码? │ │ ├── 分糖果、跳跃游戏、加油站问题? │ │ ├:贪心+排序:根据某个指标排序后贪心? │ │ └:贪心+优先队列:实时获取最优解? │ └── 应用场景 │ ├── 最小生成树:Prim和Kruskal算法中的贪心思想? │ ├── 最短路径:Dijkstra算法的贪心选择? │ ├── 数据压缩:贪心构造最优编码? │ └── 任务调度:合理安排任务顺序? ├── 九、高级数据结构与算法 │ ├── 高级数据结构 │ │ ├── 跳表(Skip List):Redis有序集合实现? │ │ ├── 布隆过滤器(Bloom Filter):判断存在性,可能误判? │ │ ├── LRU缓存:哈希表+双向链表实现? │ │ ├── 一致性哈希:分布式系统数据分片? │ │ └── 倒排索引:搜索引擎核心? │ ├── 数学与位运算 │ │ ├── 位操作技巧:判断奇偶、交换数值、找出单独数字? │ │ ├── 素数判断、最大公约数(GCD)、最小公倍数(LCM)? │ │ ├── 快速幂算法:计算a^b % mod? │ │ └── 随机数生成:拒绝采样,水塘抽样? │ └── 高级算法思想 │ ├── 分治算法:归并排序、快速排序、最近点对? │ ├── 回溯算法:N皇后、数独、全排列? │ ├── 位运算优化状态表示(状态压缩)? │ ├:扫描线算法:矩形面积并、天际线问题? │ └:摩尔投票法:寻找多数元素? └── 十、实战技巧与系统设计 ├── 解题方法论 │ ├── 解题步骤:理解题意 -> 分析 -> 选择数据结构 -> 编码 -> 测试? │ ├── 边界条件考虑:空输入、极端值、溢出? │ ├── 代码规范:变量命名、注释、模块化? │ └── 测试用例设计:常情况、边界情况、错误情况? ├── 系统设计中的应用 │ ├── LRU缓存设计:哈希表+双向链表? │ ├── 排行榜设计:跳表、有序集合? │ ├── 短网址系统:发号器、哈希算法? │ ├:朋友圈设计:并查集管理社交关系? │ └:搜索提示:Trie树实现自动补全? └── 海量数据处理 ├── 分治思想:大数据分解为小数据? ├── 哈希分片:数据均匀分布到多台机器? ├── 位图法:判断存在性,节省空间? ├:堆的应用:Top K问题? └:外部排序:内存不足时如何排序?
最新发布
09-29
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值