题解 P2656 【采蘑菇】

· · 题解

tarjan+topo+DP!!!

此题主要是让我们在缩完点后求一条从起点出发的带点权的最长路,因此我们在tarjan缩完点后直接跑拓扑排序+DP就可以了

首先是简单的tarjan缩点 码风略丑勿喷

void tarjan(int u)
{
    dfn[u] = low[u] = ++index;
    stack[++top] = u;
    instack[u] = true;
    for (int e = head[u]; e; e = edge[e].next)
    {
        int v = edge[e].to;
        if (!dfn[v])
        {
            tarjan(v);
            low[u] = std::min(low[u], low[v]);
        }
        else if (instack[v])
        {
            low[u] = std::min(low[u], low[v]);
        }
    }
    if (dfn[u] == low[u])
    {
        ++sc;
        while(stack[top] != u)
        {
            scc[stack[top]] = sc;
            instack[stack[top]] = false;
            top--;
        }
        scc[u] = sc;
        instack[u] = false;
        top--;
    }
}

缩完点后我们可以发现: 因为强连通分量里任意两点均直接或间接连通,所以这两点间连接的路径上的蘑菇我们是可以一直采,采到没有为止的。而对于连接两个强连通分量的边,边上的蘑菇我们是只能采一次的,因为我们从这条边的起点走到终点是无法再走回去的,否则这两个点应属于同一个强连通分量。

所以得出一个显而易见的结论:对于原图中的任意一条边,如果边的起点和终点在同一强连通分量中,边的权值是可以一直取的(每次乘恢复系数),而如果不在同一强连通分量中,则只取一次。

我们可以利用这个结论建一个新图!!!

遍历原图中的每一条边,如果起点终点在同一强连通分量,将边权(一直乘恢复系数直到零)加到强连通分量的点权中,如果不在同一强连通分量,就在强连通分量之间建一条有向边,边权为原边权。

for (int i = 1; i <= m; i++)
    {
        if (scc[edge[i].start] != scc[edge[i].to])
        {
            add_edge2(scc[edge[i].start], scc[edge[i].to], edge[i].w);
            in[scc[edge[i].to]]++;
        }
        else
        {
            while (edge[i].w)
            {
                val[scc[edge[i].to]] += edge[i].w;
                edge[i].w *= edge[i].k;
            }
        }
    }

现在新图建好了,那么如何在一个有向无环图中求一条带点权有起点的最长路径呢?

我们可以对新图进行一次拓扑排序,这样可以保证用来更新的点是之前已经被更新过的,但要注意一点细节。

我们设f[i]表示从i号点(强连通分量)到起点的最长路径,由于我们的起点不一定是拓扑排序的起点,也就是说起点的入度不一定为零,我们在初始化的时候,将f数组设一个极小值,将f[s(起点)]设成s的点权就可以了(ps:这里的起点指的是起点所在的强连通分量的编号)。还有一点需要注意的就是,我们要求的是带点权的最长路经,因此在更新的时候直接带着点权更新就好了。最后对f数组求一个最大值,就是我们想要的答案了。

topo + DP

void topo()
{
    for (int i = 1; i <= sc; i++)
    {
        if (!in[i])
            q.push(i);
        f[i] = -0x7fffffff;
    }
    f[scc[s]] = val[scc[s]];
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for (int e = head2[u]; e; e = edge2[e].next)
        {
            int v = edge2[e].to;
            f[v] = std::max(f[u] + edge2[e].w + val[v], f[v]);
            in[v]--;
            if (!in[v])
                q.push(v);
        }
    }
    for (int i = 1; i <= sc; i++)
        ans = std::max(ans, f[i]);
}

下面是完整代码:

sc是强连通分量的数量(编号从1-sc),scc[i]表示点i在编号为scc[i]的强连通分量中。edge是原图,edge2是新图。

#include <cstdio>
#include <queue>
#include <algorithm>

const int N_MAX = 3000000 + 7;
const int M_MAX = 5000000 + 7;

struct Edge
{
    int start, to, w, next;
    double k;
} edge[M_MAX], edge2[M_MAX];

int edge_size, head[N_MAX];

void add_edge(int u, int v, int w, double k)
{
    edge[++edge_size] = (Edge) {u, v, w, head[u], k};
    head[u] = edge_size;
}

int head2[N_MAX], edge_size2;

void add_edge2(int u, int v, int w)
{
    edge2[++edge_size2] = (Edge) {u, v, w, head2[u], 0};
    head2[u] = edge_size2;
}

int low[N_MAX], dfn[N_MAX], stack[N_MAX], top;
bool instack[N_MAX];
int scc[N_MAX], sc, index;

void tarjan(int u)
{
    dfn[u] = low[u] = ++index;
    stack[++top] = u;
    instack[u] = true;
    for (int e = head[u]; e; e = edge[e].next)
    {
        int v = edge[e].to;
        if (!dfn[v])
        {
            tarjan(v);
            low[u] = std::min(low[u], low[v]);
        }
        else if (instack[v])
        {
            low[u] = std::min(low[u], low[v]);
        }
    }
    if (dfn[u] == low[u])
    {
        ++sc;
        while(stack[top] != u)
        {
            scc[stack[top]] = sc;
            instack[stack[top]] = false;
            top--;
        }
        scc[u] = sc;
        instack[u] = false;
        top--;
    }
}

int f[N_MAX], val[N_MAX], in[N_MAX], ans;
std::queue <int> q;
int s;

void topo()
{
    for (int i = 1; i <= sc; i++)
    {
        if (!in[i])
            q.push(i);
        f[i] = -0x7fffffff;
    }
    f[scc[s]] = val[scc[s]];
    while(!q.empty())
    {
        int u = q.front();
        q.pop();
        for (int e = head2[u]; e; e = edge2[e].next)
        {
            int v = edge2[e].to;
            f[v] = std::max(f[u] + edge2[e].w + val[v], f[v]);
            in[v]--;
            if (!in[v])
                q.push(v);
        }
    }
    for (int i = 1; i <= sc; i++)
        ans = std::max(ans, f[i]);
}

int n, m, u, v, w;
double k;

int main()
{
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= m; i++)
    {
        scanf("%d %d %d %lf", &u, &v, &w, &k);
        add_edge(u, v, w, k);
    }
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            tarjan(i);
    for (int i = 1; i <= m; i++)
    {
        if (scc[edge[i].start] != scc[edge[i].to])
        {
            add_edge2(scc[edge[i].start], scc[edge[i].to], edge[i].w);
            in[scc[edge[i].to]]++;
        }
        else
        {
            while (edge[i].w)
            {
                val[scc[edge[i].to]] += edge[i].w;
                edge[i].w *= edge[i].k;
            }
        }
    }
    scanf("%d", &s);
    topo();
    printf("%d\n", ans);
}

虽然不算很长但也写了一百三十行,这也是本蒟蒻的第一篇题解,如果有不懂的地方可以私信我。