拓扑排序 + DP

A-来自wzc的简单拓扑dp_浙江农林大学第二十届程序设计竞赛暨团体程序设计天梯赛选拔赛(同步赛) (nowcoder.com)

拓扑DP模板题 

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>

using namespace std;

const int N = 100010;

int n, m;
int res, w[N];
int in[N], f[N];
vector<int> g[N];
bool st[N];

void dfs(int u, int v)
{
    if(u == n)
    {
        res = max(res, v);
        return ;
    }
    
    for(int i = 0; i < g[u].size(); i ++ )
    {
        int t = g[u][i];
        st[t] = true;
        dfs(t, v + w[t]);
        st[t] = false;
    }
}

void topsort()
{
    queue<int> q;
    q.push(0);
    while(q.size()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
插头DP是一种常见的动态规划算法,主要用于求解一些有依赖关系的问题,如图论中的最小路径覆盖、字符串匹配中的最长公共子序列等等。 插头DP的主要思想是将原问题转化为一个有向无环图(DAG)上的最长路径问题,其中每个节点表示原问题中的一个状态,每个边表示从一个状态转移到另一个状态的操作。插头DP的核心是“插头”,即在DAG中插入一些边来保证每个状态只被计算一次。 下面给出插头DP的模板代码,并详细解释每一部分的含义: ```cpp const int N = 100010; int n, m, idx; // idx表示DAG中节点的数量 int h[N], e[N], ne[N], idx; // 邻接表存储DAG int f[N]; // f[i]表示以i为终点的最长路径 int g[N]; // g[i]表示以i为起点的最长路径 int q[N], d[N]; // q存储拓扑序列,d[i]表示i的入度 bool st[N]; // st[i]表示i是否在拓扑序列中 void add(int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++; } void topsort() { int hh = 0, tt = -1; // 将所有入度为0的点加入队列 for (int i = 1; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; // 拓扑排序 while (hh <= tt) { int t = q[hh ++ ]; st[t] = true; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (-- d[j] == 0) q[ ++ tt] = j; } } } int main() { memset(h, -1, sizeof h); // 读入图 scanf("%d%d", &n, &m); while (m -- ) { int a, b; scanf("%d%d", &a, &b); add(a, b); d[b] ++ ; } // 求出DAG中的拓扑序列 topsort(); // 计算每个节点的g数组 for (int i = 0; i < idx; i ++ ) { int j = e[i]; if (st[j]) g[j] = max(g[j], f[e[i ^ 1]] + 1); } // 计算每个节点的f数组 for (int i = n; i; i -- ) { int j = q[i]; for (int k = h[j]; ~k; k = ne[k]) f[j] = max(f[j], g[e[k]] + 1); } // 求最长路径 int res = 0; for (int i = 1; i <= n; i ++ ) res = max(res, f[i]); printf("%d\n", res); return 0; } ``` 1. 声明变量 ```cpp const int N = 100010; int n, m, idx; // idx表示DAG中节点的数量 int h[N], e[N], ne[N], idx; // 邻接表存储DAG int f[N]; // f[i]表示以i为终点的最长路径 int g[N]; // g[i]表示以i为起点的最长路径 int q[N], d[N]; // q存储拓扑序列,d[i]表示i的入度 bool st[N]; // st[i]表示i是否在拓扑序列中 ``` 2. 存储图并求拓扑序列 ```cpp void add(int a, int b) { e[idx] = b; ne[idx] = h[a]; h[a] = idx ++; } void topsort() { int hh = 0, tt = -1; // 将所有入度为0的点加入队列 for (int i = 1; i <= n; i ++ ) if (!d[i]) q[ ++ tt] = i; // 拓扑排序 while (hh <= tt) { int t = q[hh ++ ]; st[t] = true; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (-- d[j] == 0) q[ ++ tt] = j; } } } int main() { memset(h, -1, sizeof h); // 读入图 scanf("%d%d", &n, &m); while (m -- ) { int a, b; scanf("%d%d", &a, &b); add(a, b); d[b] ++ ; } // 求出DAG中的拓扑序列 topsort(); } ``` 首先定义一个add函数,用于存储原图的边,同时记录每个节点的入度。然后定义一个topsort函数,用于求出DAG的拓扑序列。具体实现是通过队列来实现的,首先将所有入度为0的点加入队列,然后依次取出队首节点,并将与之相连的节点的入度减1,若入度减为0,则将该节点加入队列。最终得到的队列即为DAG的拓扑序列。 3. 计算每个节点的g数组 ```cpp for (int i = 0; i < idx; i ++ ) { int j = e[i]; if (st[j]) g[j] = max(g[j], f[e[i ^ 1]] + 1); } ``` 对于每个节点j,遍历与之相连的所有入边,对应的起点为e[i ^ 1](由于存储原图和存储DAG的边是交替存储的,所以需要异或1),若起点在拓扑序列中,则更新g[j]的值。 4. 计算每个节点的f数组 ```cpp for (int i = n; i; i -- ) { int j = q[i]; for (int k = h[j]; ~k; k = ne[k]) f[j] = max(f[j], g[e[k]] + 1); } ``` 对于每个节点j,遍历与之相连的所有出边,对应的终点为e[k],更新f[j]的值。 5. 求最长路径 ```cpp int res = 0; for (int i = 1; i <= n; i ++ ) res = max(res, f[i]); printf("%d\n", res); ``` 遍历所有节点,找到以每个节点为终点的最长路径,取最大值即为DAG的最长路径。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值