题解 P3627 【[APIO2009]抢掠计划】

咕了四个小时整整一晚上

P3627 [APIO2009]

抢掠计划(https://www.luogu.org/problemnew/show/P3627)

 



不难看出答案即为该有向图的最长链长度(允许重复

我会dp!

但是图中可能有环,不满足dp的无后效性假设

我会tarjan!

(您太强了)

在同一个强连通分量里的点一定可以互相到达,tarjan缩点之后,将每一个强联通分量看作一个点,价值就是其中所有银行的价值总和

缩点完成之后我们重新构造一个新的图,原来连接两个点的边改成连向两个点所在的强连通分量(如果在同一个强连通分量里?这就是环了不用管)

接着本来想写bfs求最长路……写着写着感觉就变成spfa了(笑哭)

最后我们算出起点到每一个强连通分量的最长路,枚举每一个点,判断有酒吧就更新答案

#include<bits/stdc++.h>
#define ll long long
#define inf 0x7fffffff
using namespace std;
int n,m; 
#define maxn 500009
vector<int> son[maxn];
int c[maxn];
bool bar[maxn];
void add(int x,int y)
{
    son[x].push_back(y);
}
int s,p;
int dfsn[maxn],lowlink[maxn];
int sta[maxn];
int top=0;
int dfs_clock=0; 
int scc_cnt=0;//有多少个强连通分量 
int scc[maxn];//每个点在那个强连通分量里 
int val[maxn];//每个点所在的强连通分量的价值总和 
int q[maxn];
int vy[maxn];//每个强连通分量的价值总和 
void dfs_scc(int x)//tarjan!!!
{
    dfsn[x]=lowlink[x]=++dfs_clock;
    sta[++top]=x;
    for(int i=0;i<son[x].size();i++)
    {
        int now=son[x][i];
        if(!dfsn[now])
        {
            dfs_scc(now);
            lowlink[x]=min(lowlink[x],lowlink[now]);
        }else if(!scc[now])
        {
            lowlink[x]=min(lowlink[x],dfsn[now]);
        }
    }
    if(lowlink[x]==dfsn[x])
    {
        scc_cnt++;
        int ans=0;
        int p=0;
        while(sta[top]!=x)
        {
            q[++p]=sta[top];//挖坑提示:q是一个用来记录的数组,把强连通分量中的点记录下来,这个q一定要开全局变量,不用memset
            //如果每次在递归中定义一个q,一轮就会爆栈!!!!本地出现蜜汁错误,洛谷ide却没问题!!
            ans+=c[sta[top]];
            scc[sta[top--]]=scc_cnt;
        }
        top--;
        scc[x]=scc_cnt;
        q[++p]=x;
        ans+=c[x];
        vy[scc_cnt]=ans;
        for(int i=1;i<=p;i++)
        {
            val[q[i]]=ans;
        }
    }
}
struct node{
    int x,y;
}e[maxn];;
vector<int> newmap[maxn];
int in[maxn],d[maxn];
struct point{
    int x,step;
};
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        e[i].x=x,e[i].y=y;
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&c[i]);
    }
    scanf("%d%d",&s,&p);
    memset(bar,0,sizeof(bar));
    for(int i=1;i<=p;i++)
    {
        int x;
        scanf("%d",&x);
        bar[x]=1;
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfsn[i])dfs_scc(i);
    }
    for(int i=1;i<=m;i++)//一条边的两个端点必须不在同一个强连通分量里 
    {
        if(scc[e[i].x]!=scc[e[i].y])
        {
            newmap[scc[e[i].x]].push_back(scc[e[i].y]);
        }
    }
     
    queue<int> q;
    q.push(scc[s]);
    d[scc[s]]=val[s];
    while(!q.empty())//广搜,不,事实上这是一个spfa 
    {
        int xx=q.front();
        int x=xx;
        q.pop();
       // printf("x:%d %d\n",x,xx.step);
        for(int i=0;i<newmap[x].size();i++)
        {
            int to=newmap[x][i];
            if(d[to]>=d[x]+vy[to])continue; //类似于松弛的操作…… 
            d[to]=max(d[to],d[x]+vy[to]);
            q.push(to);
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(bar[i])//如果这个点有酒吧,那么我们就统计一下它所在的强连通分量的答案 
        {
            ans=max(ans,d[scc[i]]);
        }
    }
    printf("%d\n",ans);
    return 0;
}

 

上一篇:tarjan复习笔记


下一篇:Tarjan笔记