图论:P2656采蘑菇 Tarjan缩点+SPFA/DFS树形DP
P2656采蘑菇
题目:
思路分析:
这题就可以用tarjan缩点,把有环图转化为无环图,缩点可以参考我的图论:P3387【模板】缩点 tarjan - 朱朱成 - 博客园 (cnblogs.com)的题解做法,但是这一题有点区别的是,他没有点权,只有边权,要缩的是边权,所以我们在构建新图的时候判断,如果这两个点同属一个联通块,就加入他们的总权值,也就是可以重复采摘的总权值,我们构建结构体来存储总权值,注意因为double只有一位小数,最后把double*10,先计算,最后再除10,这样不会丢失精度,否则最后会错一个点!亲测。最后一个图就变成了,联通块的点权不等于0,单结点的点权=0,联通块的单结点的边权!=0.就变成了有点权有边权的一个图了。然后用DFS树形DP求解或者SPFA。
一、tarjan:
//tarjan int belong[maxn], dfn[maxn], low[maxn], num; bool vis[maxn]; int l; stack<int>s; void tarjan(int u) { dfn[u] = low[u] = ++num; s.push(u); for (int i = old_head[u]; i; i = old_edge[i].nex) { int j = old_edge[i].en; if (!dfn[j]) { tarjan(j); low[u] = min(low[u], low[j]); } else if (!vis[j]) { low[u] = min(dfn[j], low[u]); } } if (dfn[u] == low[u]) { ++l; // cout << "联通" << l; while (1) { //cout<< s.top() << " "; belong[s.top()] = l; vis[s.top()] = 1; if (s.top() == u) { s.pop(); break; } s.pop(); } // cout << endl; } }
二、缩点
//缩点 构造新图 int sum[maxn];//把邻接点间的边权缩成点权 int len; mushroom new_edge[maxm];//建立新邻接图 int new_head[maxn]; void suodian() { for (int i = 1; i <= m; ++i) { int begins = belong[old_edge[i].be]; int ends = belong[old_edge[i].en]; if (begins == ends) { sum[begins] += old_edge[i].sumval; continue;//是关键 保证了只会插入一个点在新图 //cout << sum[begins] << endl; } len++; mushroom temp(begins, ends, old_edge[i].val, 0, new_head[begins]); //cout << begins << ends << old_edge[i].val << 0 << new_head[begins] << endl; new_edge[len] = temp; new_head[begins] = len;//更新头 } }
三、SPFA:
//SPFA int dis[maxn]; bool exist[maxn]; int SPFA(int x) { for (int i = 0; i <= n; ++i)dis[i] = -1;//要求最长路径 初始化为很小的值 dis[x] = sum[x]; queue<int>q; int ans = max(dis[x], ans); q.push(x); exist[x] = 1; while (!q.empty()) { int temp = q.front(); q.pop(); exist[temp] = 0; for (int i = new_head[temp]; i; i = new_edge[i].nex) { int j = new_edge[i].en; if (dis[j] < dis[temp] + new_edge[i].val + sum[j]) { dis[j] = dis[temp] + new_edge[i].val + sum[j]; ans = max(dis[j], ans); if (!exist[j])//如果队列里没有这个元素 就入队 { q.push(j); exist[j] = 1; } } } } return ans; }
四、DFS树形DP:
//记忆化深搜找最大值 int dp[maxn]; void dfs(int x) { if (dp[x])return; int ans = 0; for (int i = new_head[x]; i; i = new_edge[i].nex) { int j = new_edge[i].en; dfs(j); ans = max(ans, new_edge[i].val + dp[j]); } ans += sum[x];//+上点权 dp[x] = ans; return; }
完整代码:
①SPFA:
1 //SPFA: 2 3 #include<iostream> 4 #include<algorithm> 5 #include<cstring> 6 #include<vector> 7 #include<stack> 8 #include<queue> 9 using namespace std; 10 const int maxn = 8 * 1e4 + 5; 11 const int maxm = 2 * 1e5 + 5; 12 struct mushroom 13 { 14 int be, en, val, sumval, nex; 15 mushroom()//构造函数 16 { 17 ; 18 } 19 mushroom(int a, int b, int c, int cc, int d) 20 { 21 be = a; 22 en = b; 23 val = c; 24 sumval = cc; 25 nex = d; 26 } 27 }old_edge[maxm]; 28 int old_head[maxn]; 29 int ind; 30 int n, m, x; 31 int a, b, c; 32 double d; 33 34 //tarjan 35 int belong[maxn], dfn[maxn], low[maxn], num; 36 bool vis[maxn]; 37 int l; 38 stack<int>s; 39 void tarjan(int u) 40 { 41 dfn[u] = low[u] = ++num; 42 s.push(u); 43 for (int i = old_head[u]; i; i = old_edge[i].nex) 44 { 45 int j = old_edge[i].en; 46 if (!dfn[j]) 47 { 48 tarjan(j); 49 low[u] = min(low[u], low[j]); 50 } 51 else if (!vis[j]) 52 { 53 low[u] = min(dfn[j], low[u]); 54 } 55 } 56 if (dfn[u] == low[u]) 57 { 58 ++l; 59 // cout << "联通" << l; 60 while (1) 61 { 62 //cout<< s.top() << " "; 63 belong[s.top()] = l; 64 vis[s.top()] = 1; 65 if (s.top() == u) 66 { 67 s.pop(); 68 break; 69 } 70 s.pop(); 71 } 72 // cout << endl; 73 } 74 } 75 76 //缩点 构造新图 77 int sum[maxn];//把邻接点间的边权缩成点权 78 int len; 79 mushroom new_edge[maxm];//建立新邻接图 80 int new_head[maxn]; 81 void suodian() 82 { 83 for (int i = 1; i <= m; ++i) 84 { 85 int begins = belong[old_edge[i].be]; 86 int ends = belong[old_edge[i].en]; 87 if (begins == ends) 88 { 89 sum[begins] += old_edge[i].sumval; 90 continue;//是关键 保证了只会插入一个点在新图 91 //cout << sum[begins] << endl; 92 } 93 len++; 94 mushroom temp(begins, ends, old_edge[i].val, 0, new_head[begins]); 95 //cout << begins << ends << old_edge[i].val << 0 << new_head[begins] << endl; 96 new_edge[len] = temp; 97 new_head[begins] = len;//更新头 98 99 } 100 } 101 102 //SPFA 103 int dis[maxn]; 104 bool exist[maxn]; 105 int SPFA(int x) 106 { 107 for (int i = 0; i <= n; ++i)dis[i] = -1;//要求最长路径 初始化为很小的值 108 dis[x] = sum[x]; 109 queue<int>q; 110 int ans = max(dis[x], ans); 111 q.push(x); 112 exist[x] = 1; 113 while (!q.empty()) 114 { 115 int temp = q.front(); 116 q.pop(); 117 exist[temp] = 0; 118 for (int i = new_head[temp]; i; i = new_edge[i].nex) 119 { 120 int j = new_edge[i].en; 121 if (dis[j] < dis[temp] + new_edge[i].val + sum[j]) 122 { 123 dis[j] = dis[temp] + new_edge[i].val + sum[j]; 124 ans = max(dis[j], ans); 125 if (!exist[j])//如果队列里没有这个元素 就入队 126 { 127 q.push(j); 128 exist[j] = 1; 129 } 130 } 131 132 } 133 } 134 return ans; 135 } 136 137 int main() 138 { 139 ios::sync_with_stdio(false); 140 cin >> n >> m; 141 for (int i = 1; i <= m; ++i)//读入邻接关系 142 { 143 cin >> a >> b >> c >> d; 144 int e = d * 10; 145 int cc = 0; 146 int c_temp = c;//不能直接用c 不然下面存的c值是0 147 while (c_temp) 148 { 149 cc += c_temp; 150 c_temp *= e;//关键点 把double转化为int 再处于10 否则数据一大精度就会出现问题 151 c_temp /= 10; 152 } 153 ind++; 154 mushroom temp(a, b, c, cc, old_head[a]); 155 old_head[a] = ind; 156 old_edge[ind] = temp; 157 } 158 cin >> x;//输入起点 159 tarjan(x); 160 suodian(); 161 cout << SPFA(belong[x]); 162 return 0; 163 }
②:树形DP dfs
//树形dp #include<iostream> #include<algorithm> #include<cstring> #include<vector> #include<stack> #include<queue> using namespace std; const int maxn = 8 * 1e5 + 5; const int maxm = 2 * 1e6 + 5; struct mushroom { int be, en, val, sumval, nex; mushroom()//构造函数 { ; } mushroom(int a, int b, int c, int cc, int d) { be = a; en = b; val = c; sumval = cc; nex = d; } }old_edge[maxm]; int old_head[maxn]; int ind; int n, m, x; int a, b, c; double d; //tarjan int belong[maxn], dfn[maxn], low[maxn], num; bool vis[maxn]; int l; stack<int>s; void tarjan(int u) { dfn[u] = low[u] = ++num; s.push(u); for (int i = old_head[u]; i; i = old_edge[i].nex) { int j = old_edge[i].en; if (!dfn[j]) { tarjan(j); low[u] = min(low[u], low[j]); } else if (!vis[j]) { low[u] = min(dfn[j], low[u]); } } if (dfn[u] == low[u]) { ++l; // cout << "联通" << l; while (1) { //cout<< s.top() << " "; belong[s.top()] = l; vis[s.top()] = 1; if (s.top() == u) { s.pop(); break; } s.pop(); } // cout << endl; } } //缩点 构造新图 int sum[maxn];//把邻接点间的边权缩成点权 int len; mushroom new_edge[maxm];//建立新邻接图 int new_head[maxn]; void suodian() { for (int i = 1; i <= m; ++i) { int begins = belong[old_edge[i].be]; int ends = belong[old_edge[i].en]; if (begins == ends) { sum[begins] += old_edge[i].sumval; continue;//是关键 保证了只会插入一个点在新图 //cout << sum[begins] << endl; } len++; mushroom temp(begins, ends, old_edge[i].val, 0, new_head[begins]); //cout << begins << ends << old_edge[i].val << 0 << new_head[begins] << endl; new_edge[len] = temp; new_head[begins] = len;//更新头 } } //记忆化深搜找最大值 int dp[maxn]; void dfs(int x) { if (dp[x])return; int ans = 0; for (int i = new_head[x]; i; i = new_edge[i].nex) { int j = new_edge[i].en; dfs(j); ans = max(ans, new_edge[i].val + dp[j]); } ans += sum[x];//+上点权 dp[x] = ans; return; } int main() { ios::sync_with_stdio(false); cin >> n >> m; for (int i = 1; i <= m; ++i)//读入邻接关系 { cin >> a >> b >> c >> d; int e = d * 10; int cc = 0; int c_temp = c;//不能直接用c 不然下面存的c值是0 while (c_temp) { cc += c_temp; c_temp *= e; c_temp /= 10; } ind++; mushroom temp(a, b, c, cc, old_head[a]); old_head[a] = ind; old_edge[ind] = temp; } cin >> x;//输入起点 tarjan(x); suodian(); dfs(belong[x]); cout << dp[belong[x]]; return 0; }
最后对比一下时间,空间复杂度
SPFA还是略快一点,而且DP空间复杂度大,浪费空间,所以还是SPFA好一些。