根据题目意思,我们只需要找出一条点权最大的路径就行了,不限制点的个数。那么考虑对于一个环上的点被选择了,一整条环是不是应该都被选择,这一定很优,能选干嘛不选。很关键的是题目还允许我们重复经过某条边或者某个点,我们就不需要考虑其他了。因此整个环实际上可以看成一个点(选了其中一个点就应该选其他的点)
拓扑排序
对一个有向无环图(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 ", ans); return 0; }