(板子)缩点 + DAG上的DP(深搜)luogu P3387

板子传送门

 

根据题目意思,我们只需要找出一条点权最大的路径就行了,不限制点的个数。那么考虑对于一个环上的点被选择了,一整条环是不是应该都被选择,这一定很优,能选干嘛不选。很关键的是题目还允许我们重复经过某条边或者某个点,我们就不需要考虑其他了。因此整个环实际上可以看成一个点(选了其中一个点就应该选其他的点)

 

拓扑排序

  对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边(u,v)∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

照个人理解,拓扑排序通常是在DAG图中寻找一个适合的解决问题的顺序。

如何实现拓扑排序

方法1:BFS(SPFA优化)

1、先寻找入度为0的点,把它加入队列。

2、搜寻队列,把队列的点G删去,则如果有点的入度有G点的话,入度- -,当发现又出现入度为0的点时,将该点加入队列。

3、拓扑排序的结果为该队列,在执行删点操作的时候存储在一个数组及可。

方法2:记忆化搜索

大多数情况下,并不需要显式的拓扑排序

考虑朴素的回溯算法

若从一个给定的点出发,得到的结果是一样的

因此对于每个点,计算完成后可以把结果保存起来,之后直接返回查表的结果即可

 

拓扑排序伪代码(1):

Topological_sort(G){
    统计图G中每个点的入度(可计算重边,但不可计算自环),记为degree[i]
    初始化queue和result为空的队列,并将所有degree为0的点加入queue
    while (!queue.empty()){
        u = queue.pop() // 队首
        result.push(u)
        for e 是u的出边(若上面计算了重边,这里也要算,与上面一致)
        v是e的指向的点
        degree[v]--
        if (degree[v] == 0) queue.push(v)
    }
    return result
}

伪代码(2)

calculate(u){
    if (u 已经搜索过) return table[u]
    ans = -inf
    for (v 是u的出边指向的点)
    ans = max(ans, value[u] + calculate(v))
    标记u已经搜索过
    table[u] = ans
    return ans
}
for (i 是G的所有节点)
result = max(result, calculate(i))
print(result)

 

为什么要dp

因为题目说了啊(),其实也很明显啦,对每个点都不断用他的入边的点更新他,取最大值,f[i]表示i点(缩点后)的经过点和最大值。

方程:

w代表当前点,rdr数组代表w点的入边的点,dis数组是权值。
f[w]=max(f[w],f[rdr[w][j-1]]+dis_[w]);

 

完整AC代码:

#include<bits/stdc++.h>
using namespace std;
#define N 100010

inline int read(){
    int x = 0,s = 1;
    char c = getchar();
    while(!isdigit(c)){
        if(c == '-')s = -1;
        c = getchar();
    }
    while(isdigit(c)){
        x = (x << 1) + (x << 3) + (c ^ '0');
        c = getchar();
    }
    return x * s;
}

struct node{
    int u, v;
    int next;
} t[N];
int f[N];
int dfn[N], scc[N], low[N];
int stac[N], top = 0; 
bool vis[N];
int w[N], sum[N];//单点 + 缩点的值 

int bian = 0;
inline void add(int u, int v){
    bian++;
    t[bian].u = u;
    t[bian].v = v;
    t[bian].next = f[u];
    f[u] = bian;
    return ;
}

int cnt = 0, cac = 0;
void tarjan(int now){
    dfn[now] = low[now] = ++cnt;
    vis[now] = 1;
    stac[++top] = now;
    for(int i = f[now]; i; i = t[i].next){
        int u = t[i].u,v = t[i].v;
        if(!dfn[v]){
            tarjan(v);
            low[u] = min(low[u], low[v]);
        }
        else if(vis[v]) low[u] = min(low[u], dfn[v]);
    }
    if(dfn[now] == low[now]){
        int cur;
        cac++;
        do{
            cur = stac[top--];
            scc[cur] = cac;
            vis[cur] = 0;
            sum[cac] += w[cur];
        }while(cur != now);
    }
    return ;
} 

int dp[N];
void search(int now){
    if(dp[now])return;
    dp[now] = sum[now];
    int maxn = 0;
    for(int i = f[now]; i;i = t[i].next){
        int v = t[i].v;
        if(!dp[v])search(v);
        maxn = max(dp[v], maxn);
    }
    dp[now] += maxn;
    return; 
}

int main(){
    int n = read(), m = read();
    for(int i = 1;i <= n; i++)
        w[i] = read();
    for(int i = 1;i <= m; i++){
        int x = read(), y = read();
         add(x, y);
    } 
    for(int i = 1;i <= n; i++)
        if(!dfn[i]) tarjan(i);
    bian = 0;
    memset(f, 0, sizeof(f));
    for(int i = 1;i <= m; i++){
        t[i].next = 0;
    }
    for(int i = 1;i <= m; i++){
        int u = t[i].u, v = t[i].v;
        if(scc[u] != scc[v]){
            add(scc[u], scc[v]);
        }
    }
    int ans = -(~0u >> 1);
    for(int i = 1;i <= cac; i++){//注意是缩点的个数,这里是新图了 
        if(!dp[i]){
            search(i);//进行记忆化搜索 
            ans = max(ans, dp[i]);
        }
    }
    printf("%d\n", ans);
    return 0;
}

 

posted @ 2020-04-04 22:51  雪之下,树之旁  阅读(165)  评论(0编辑  收藏  举报