圖論:P2656採蘑菇 Tarjan縮點+SPFA/DFS樹形DP
阿新 • • 發佈:2022-05-08
P2656採蘑菇
題目傳送門:P2656 採蘑菇 - 洛谷 | 電腦科學教育新生態 (luogu.com.cn)
題目:
思路分析:
這題就可以用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好一些。