博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
图上DP
阅读量:3952 次
发布时间:2019-05-24

本文共 6697 字,大约阅读时间需要 22 分钟。

文章目录

DAG上DP

先对图进行预处理,再进行DP。一般都有两种套路,拓扑序或如Dijkstra或SPFA的最短路算法

拓扑序

题意:一个有向图中,1->2之间有多少条不同路径。(有可能是0或inf)

思路:在1->2的任意路径上有环即为inf。但是环若是在死路上就没影响。所以先在正向图从1开始DFS所有点,并标记1。在反向图从2开始DFS所有点,并标记为2。最后,被1和2都标记过的点即为路径上的点。其他点不参与后面运算。

后面直接按拓扑序DP即可,若最后2的入度不为0,说明路径上有环,inf。否则输出dp[2].

#include
#define endl '\n'#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);using namespace std;const int N = 1e5 +5, mod = 1e9;const double eps = 1e-10;using LL = long long;int n, m;vector
G[N], NG[N];int vis[N], nvis[N], f[N], in[N];void dfs(vector
Path[], int flag[], int u){
flag[u] = 1; for(int v : Path[u]){
if(!flag[v]){
dfs(Path, flag, v); } }}void topoSort(){
for(int i = 1; i <= n; ++i){
if(!vis[i])continue; for(int u : G[i]){
if(!vis[u])continue; ++in[u]; } } queue
Q; for(int i = 1; i <= n; ++i){
nvis[i] = 0; if(vis[i] && !in[i]) Q.push(i); } f[1] = 1; while(!Q.empty()){
int u = Q.front(); Q.pop(); if(u == 2)return; for(int v : G[u]){
if(!vis[v])continue; f[v] = (f[v] + f[u]) % mod; if(--in[v] == 0)Q.push(v); } } cout << "inf" << endl; exit(0);}int main(){
IOS; cin >> n >> m; for(int x, y, i = 1; i <= m; ++i){
cin >> x >> y; G[x].push_back(y); NG[y].push_back(x); } dfs(G, vis, 1); dfs(NG, nvis, 2); for(int i = 1; i <= n; ++i){
//cout << i << ": " << vis[i] << " " << nvis[i] << endl; vis[i] = vis[i] + nvis[i] == 2 ? 1 : 0; } if(!vis[2]){
cout << 0 << endl; exit(0); } topoSort(); cout << f[2] << endl;}

SPFA

普通图上DP

普通的图上面DP,由于有环的存在,一部分DP具有后效性,故需要用最短路算法(Dijkstra或SPFA)来解决。另一部分DP,则有可能是图上DFS,记忆化搜索

SPFA

题意:求1->2->1的路径,使得路过的点最少。如下图,路过6个点。
在这里插入图片描述
思路:定义 f [ i ] [ j ] f[i][j] f[i][j] 1 → i → j → 1 1→i→j→1 1ij1的最少经过点数。 d [ i ] [ j ] d[i][j] d[i][j] i i i j j j之间的最短路。则 f [ a ] [ b ] f[a][b] f[a][b] 1 → a → b → 1 1→a→b→1 1ab1可以通过 1 → x → y → a   →   b → x → y → 1 1→x→y→a\ →\ b→x→y→1 1xya  bxy1更新,即 f [ a ] [ b ] = f [ x ] [ y ] + d [ y ] [ a ] + d [ a ] [ b ] + d [ b ] [ x ] − 1 f[a][b] = f[x][y] + d[y][a] + d[a][b] + d[b][x] - 1 f[a][b]=f[x][y]+d[y][a]+d[a][b]+d[b][x]1。这样就可囊括以下几种情况。

  • 1 − > 2 1->2 1>2 2 − > 1 2->1 2>1上都包含 x → y x→y xy。如上图中的 3 → 4 3→4 34
  • 1 − > 2 1->2 1>2包含 x → y x→y xy 2 − > 1 2->1 2>1上包含 y → x y→x yx,如下图。
    1 → 2 → 2 → 1 1→2→2→1 1221通过 1 → y → y → 2 → 2 → y → y → 1 1→y→y→2→2→y→y→1 1yy22yy1更新,即通过 1 → y → y → 1 1→y→y→1 1yy1得到。
    1 → y → y → 1 1→y→y→1 1yy1同理,通过 1 → x → x → y → y → x → x → 1 1→x→x→y→y→x→x→1 1xxyyxx1更新,即通过 1 → x → x → 1 1→x→x→1 1xx1得到。

做法:通过Floyd得到 d [ i ] [ j ] d[i][j] d[i][j]。初始化 f [ 1 ] [ 1 ] = 1 f[1][1]=1 f[1][1]=1,由于DP的后效性,使用SPFA进行DP,每次DP都更新 f [ 1 ⋯ n ] [ 1 ⋯ n ] f[1\cdots n][1\cdots n] f[1n][1n]所有的点(不包括f[x][y]),直到所有f都无法更新。最后答案就是 f [ 2 ] [ 2 ] f[2][2] f[2][2]

在这里插入图片描述

#include
#define endl '\n'#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);using namespace std;const int N = 1e3 +5, mod = 1e9;int n, m;vector
G[N];int d[N][N], f[N][N], in[N][N];struct Node{
int x, y; Node(int x = 0, int y = 0):x(x), y(y) {
}};int main(){
IOS; int n, m; cin >> n >> m; memset(d, 0x0f, sizeof(d)); for(int x, y, i = 1; i <= m; ++i){
cin >> x >> y; G[x].push_back(y); d[x][y] = 1; } for(int i = 1; i <= n; ++i) d[i][i] = 0; for(int k = 1; k <= n; ++k) for(int i = 1; i <= n; ++i) for(int j = 1; j <= n; ++j) d[i][j] = min(d[i][j], d[i][k] + d[k][j]); memset(f, 0x7f, sizeof(f)); queue
pq; f[1][1] = 1; pq.emplace(1, 1); in[1][1] = 1; while(!pq.empty()){
int x = pq.front().x, y = pq.front().y; in[x][y] = 0; pq.pop(); for(int a = 1; a <= n; ++a){
for(int b = 1; b <= n; ++b){
if(a==x && b==y)continue; if(f[a][b] > f[x][y] + d[y][a] + d[a][b] + d[b][x] - 1){
f[a][b] = f[x][y] + d[y][a] + d[a][b] + d[b][x] - 1; if(in[a][b])continue; in[a][b] = 1; pq.emplace(a, b); } } } } cout << f[2][2] << endl;}

记忆化搜索

题意:设1->n之间的最短路为d,求1->n之间长度不超过d+k的不同路径数量。(有边权为0的边)
思路:形式明显是 f [ v ] [ w ] f[v][w] f[v][w],其中v是点,w是超出d的长度。但具体意义比较难。代表从 1 1 1 v v v的距离为 d i s [ v ] + w dis[v]+w dis[v]+w的路径数。求解过程就是在反向图上记忆化搜索。
f [ u ] [ e x ] = ∑ v → u f [ v ] [ d i s [ u ] + e x − d i s [ v ] − C v → u ] f[u][ex] = \sum_{v\to u} f[v][dis[u] + ex - dis[v] - C_{v\to u}] f[u][ex]=vuf[v][dis[u]+exdis[v]Cvu]
特别的,有可能有零环,即DFS到 u , e x u,ex u,ex时,访问到了DFS树上的祖先节点,故当进入DFS时,标记 f [ u ] [ e x ] = − 2 f[u][ex]=-2 f[u][ex]=2,若最后访问到了-2节点则证明有零环。

#include
#define endl '\n'#define IOS ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);using namespace std;typedef long long LL;const int N = 1e5 +5, mod = 1e9;vector
> G[N], NG[N];LL dis[N], f[N][55];int in[N];LL n, m, k, p, flag;void spfa(int s, int t){
memset(dis, 0x0f, sizeof(dis)); dis[s] = 0; queue
Q; Q.push(s); while(!Q.empty()){
int u = Q.front(); in[u] = 0; Q.pop(); for(auto it : G[u]){
int c = it.second, v = it.first; if(dis[u] + c < dis[v]){
dis[v] = dis[u] + c; if(in[v])continue; in[v] = 1; Q.emplace(v); } } }}int dfs(int u, int ex){
if(~f[u][ex])return f[u][ex]; f[u][ex] = -2; LL ans = u == 1 && ex == 0; for(auto it : NG[u]){
int c = it.second, v = it.first; if(dis[u] + ex - dis[v] - c < 0)continue; if(f[v][dis[u] + ex - dis[v] - c] == -2){
flag = 0; return 0; } ans += dfs(v, dis[u] + ex - dis[v] - c); ans %= p; } return f[u][ex] = ans;}int main(){
IOS; int T; cin >> T; while(T--){
cin >> n >> m >> k >> p; for(int i = 1; i <= n; ++i){
G[i].clear(); NG[i].clear(); } for(int i = 1, x, y, z; i <= m; ++i){
cin >> x >> y >> z; G[x].emplace_back(y, z); NG[y].emplace_back(x, z); } spfa(1, n); LL ans = 0; memset(f, -1, sizeof(f)); flag = 1; for(int i = 0; i <= k && flag; ++i){
ans += dfs(n, i); ans %= p; } cout << (flag ? ans : -1) << endl; }}

转载地址:http://cwuzi.baihongyu.com/

你可能感兴趣的文章
ubuntu安装命令
查看>>
和上司沟通必备8个黄金句
查看>>
联系查看两张卡的未接电话记录
查看>>
Python 3 之多线程研究
查看>>
APP第三方登录实现步骤
查看>>
KVO & KVC 比较 - KVC
查看>>
iOS-tableView联动
查看>>
iOS--Masonry解决 tableViewCell 重用时约束冲突
查看>>
git 与 svn 的主要区别!
查看>>
iOS-截屏,从相册选择图片,制作磨砂效果图片
查看>>
iOS-截取字符串中两个指定字符串中间的字符串
查看>>
数据库-数据库操作(使用FMDB)
查看>>
FMDB介绍以及在 swift 中的数据库操作
查看>>
iOS运行时机制(附Demo演练)
查看>>
宽字符串输出问题
查看>>
将整数转换为宽字符串
查看>>
在类中定义enum实现整数常量功能
查看>>
suse11通过安装最新内核可以上网的经验
查看>>
SUSE静态配置IP成功上网
查看>>
通过sleep让程序等待外部条件改变
查看>>