非递归!APIO2009atm[抢掠计划]题解

题目描述

这里写图片描述

  • 输入描述 Input Description
    第一行包含两个整数N、M。N 表示路口的个数,M 表示道路条数。接下来
    M 行,每行两个整数,这两个整数都在1 到N 之间,第i+1 行的两个整数表示第
    i 条道路的起点和终点的路口编号。接下来N 行,每行一个整数,按顺序表示每
    个路口处的ATM 机中的钱数。接下来一行包含两个整数S、P,S 表示市中心的
    编号,也就是出发的路口。P 表示酒吧数目。接下来的一行中有P 个整数,表示
    P 个有酒吧的路口的编号。

  • 输出描述 Output Description
    输出一个整数,表示Banditji 从市中心开始到某个酒吧结束所能抢劫的最多
    的现金总数。

  • 样例输入 Sample Input
    6 7
    1 2
    2 3
    3 5
    2 4
    4 1
    2 6
    6 5
    10
    12
    8
    16
    1 5
    1 4
    4 3 5 6

  • 样例输出 Sample Output
    47

  • 数据范围及提示 Data Size & Hint
    50%的输入保证N, M<=3000。所有的输入保证N, M<=500000。每个ATM
    机中可取的钱数为一个非负整数且不超过4000。输入数据保证你可以从市中心
    沿着Siruseri 的单向的道路到达其中的至少一个酒吧。

题解
  • 题意还是比较裸的,在环上的ATM机是一定都要抢的。首先是求强连通分量缩点,然后从起点即市中心开始spfa最长路或者DP,目的是求出最终停在每个强连通分量时所抢得的总钱数;然后读入所有酒吧,把有酒吧的强连通分量作为终点,枚举一下取钱数的最大值即可。

  • 然而数据规模使得递归的tarjan算法被卡住了,所以要手动改成非递归。

  • 首先用一个大栈(可以理解成系统栈的替代品,区分递归tarjan用到的小栈)保存当前正在tarjan的点。设当前正在tarjan点x,则先把x入大栈,同时入小栈,标记x的dfn和low的值,并作出相应标记表示x已经入了小栈(就像递归tarjan一样);

  • 其次,开始非递归主过程:
    大栈非空时,从栈顶取出一个元素t,但不要弹栈;
    访问t所有出边指向的结点a,若dfn[a]等于0,就把a压入大栈,相当于递归tarjan中的递归调用tarjan(a),然后break掉,进入下文中的“然后”,因为递归tarjan中此时low(a)的值会在递归过程中算出,非递归tarjan中low(a)的值还不知道;
    若dfn[a]不等于0,不急,先什么也不干,下文中“然后”会处理;

  • 然后,t所有出边都已被遍历,这时,判断t是否是大栈的栈顶;
    如果是,则说明t没有进入任何递归过程,即t所有出边指向的点都已经被tarjan完了,这时应该确定low[t]的值;
    访问t所有出边指向的结点a,若dfn[a] > dfn[t],说明上文“其次”中访问a时a的dfn是0,所以执行递归tarjan中调用完tarjan(a)之后的操作——low[t] = min(low[t], low[a]);
    若dfn[a]<dfn[t],说明上文“其次”中访问a时a的dfn不是0,所以执行递归tarjan中dfn[a] != 0之后的操作——若a在小栈中则low[t] = min(low[t], dfn[a]);
    这样,我们确定了low[t]的值;
    如果此时dfn[t] == low[t],则从小栈里取出这个强连通分量,过程和递归tarjan完全相同,故不再赘述,此后才可以在大栈中弹掉t,因为t所属的强连通分量被算出来以后,tarjan(t)才真正结束,t才可以从大栈里取出;
    如果t不是大栈的栈顶,则回到上文中的“其次”,相当于递归调用t的出边指向的某个终点;

  • 最后,“其次”时发现大栈为空,算法结束。

  • Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
#define nil 0
#define N 500005
using namespace std;
int n, m, csh[N], S, P, f[N];//题目中的变量,csh代表缩点前每个点有多少钱,f[i]是第i个强连通分量为终点能抢多少钱 
int u[N << 2], v[N << 2], nxt[N << 2], pnt[N], e;//存原图用的邻接表 
int dfn[N], low[N], isin[N], hcash[N], tot, indx;//tarjan算法,hcash[i]表示第i个强连通分量一共有多少钱 
bool instack[N], vis[N];//instack存是否在小栈中,vis是spfa用的 
int Sta[N], stop;//小栈 
vector <int> g[N];//存缩点后的新图用的vector 
stack <int> s;//大栈 
void add(int a, int b)//向原图中加边 
{
    u[++e] = a; v[e] = b;
    nxt[e] = pnt[a]; pnt[a] = e;
}
/*void tarjan(int k)//递归tarjan 
{
    dfn[k] = low[k] = ++indx;
    instack[k] = true;
    Sta[++stop] = k;
    for(int j = pnt[k]; j != nil; j = nxt[j])
    {
        if(dfn[v[j]] == nil)
        {
            tarjan(v[j]);
            low[k] = min(low[k], low[v[j]]);
        }
        else
        {
            if(instack[v[j]] && dfn[v[j]] < low[k])
            {
                low[k] = dfn[v[j]];
            }
        }
    }
    if(dfn[k] == low[k])
    {
        ++tot;
        int j;
        do
        {
            j = Sta[stop--];
            instack[j] = false;
            isin[j] = tot;
            hcash[tot] += csh[j];
        } while(j != k);
    }
}*/ 
void tarjan(int x)//非递归tarjan 
{
    s.push(x);
    dfn[x] = low[x] = ++indx;
    Sta[++stop] = x;
    instack[x] = true;
    while(!s.empty())
    {
        int t = s.top();
        for(int i = pnt[t]; i != nil; i = nxt[i])
        //这里可以开一个和ISAP最大流相似的当前弧优化,不过似乎用处不大 
        {
            if(dfn[v[i]] == 0)//如果未访问,则压栈,准备访问 
            {
                dfn[v[i]] = low[v[i]] = ++indx;
                Sta[++stop] = v[i]; instack[v[i]] = true;
                s.push(v[i]);
                break;//一定要break,意思是立即递归调用tarjan(a) 
            }
        }
        if(t == s.top())
        {
            for(int i = pnt[t]; i != nil; i = nxt[i])
            {
                if(dfn[v[i]] > dfn[t]) low[t] = min(low[t], low[v[i]]);
                //这句对应递归tarjan中的if(dfn[v[j]] != 0)的情况 
                else if(instack[v[i]]) low[t] = min(low[t], dfn[v[i]]);
            }
            if(dfn[t] == low[t])
            {
                ++tot;
                int j;
                do
                {
                    j = Sta[stop--];
                    instack[j] = false;
                    isin[j] = tot;
                    hcash[tot] += csh[j];
                } while(j != t);
            }
            s.pop();
        }
    }
}
void init()
{
    int a, b;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; ++i)
    {
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", &csh[i]);
    }
    scanf("%d%d", &S, &P);
    for(int i = 1; i <= n; ++i)
    {
        if(dfn[i] == 0)
        {
            tarjan(i);
        }
    }
    for(int i = 1; i <= n; ++i)
    {
        for(int j = pnt[i]; j != nil; j = nxt[j])
        {
            if(isin[i] != isin[v[j]])
            {
                g[isin[i]].push_back(isin[v[j]]);
            }
        }
    }
}
void work()//spfa最长路,以前的DP写挫了。。。 
{
    queue <int> Q;
    memset(vis, 0, sizeof(vis));
    memset(f, 0, sizeof(f));
    f[isin[S]] = hcash[isin[S]];
    Q.push(isin[S]);
    vis[isin[S]] = true;
    while(!Q.empty())
    {
        int t = Q.front();
        Q.pop();
        vis[t] = false;
        for(int i = 0; i < g[t].size(); ++i)
        {
            if(f[g[t][i]] < f[t] + hcash[g[t][i]])
            {
                f[g[t][i]] = f[t] + hcash[g[t][i]];
                if(!vis[g[t][i]])
                {
                    vis[g[t][i]] = true;
                    Q.push(g[t][i]);
                }
            }
        }
    }
    int ans = 0, a;
    for(int i = 1; i <= P; ++i)
    {
        scanf("%d", &a);
        ans = max(ans, f[isin[a]]);
    }
    printf("%d\n", ans);
}
int main()
{
    init();
    work();
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值