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

posted @ 2022-05-08 11:04  朱朱成  阅读(38)  评论(0编辑  收藏  举报