1. 程式人生 > 其它 >圖論:P2656採蘑菇 Tarjan縮點+SPFA/DFS樹形DP

圖論:P2656採蘑菇 Tarjan縮點+SPFA/DFS樹形DP

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好一些。