洛谷 P3119草鉴定

题目大意有n个草场,有向图连接,从第一个草场出发,最终回到第一个草场。每经过一个草场就可以吃一次草,问假如可以偷偷逆向走一条道路,最多可以吃到多少次草

刚看到这道题,第一想法就是Tarjan缩点,求强连通分量。但是题目有个额外要求,可以有一次的机会逆向走一条道路。刚开始有点不会处理这个额外的要求。有模糊的想法但不对,看到别人的题解后感觉可以行的通。先进行Tarjan缩点,构造一个DAG图,然后分类,枚举。做出分层图。(头一回见分层图这个东东)

做法我就直接粘出来了。考虑一张图,将这个图复制一份,点的编号从1~N到(N+1)~(N+N)。然后在两层图中连边。对于原图上的每一条边,从原图的指向点到新图的起始点连一条边,(我的理解是即若原图u -> v有一条边,那么分层图中原图【下面的图】到新图【上面的图】存在v -> u'要连一条边)边权与原边相同,代表逆向走一条边。逆向走了一条边,就不能再逆向走了,所以从上面的一层(新图)无法回到下面的一层。最后跑一遍SPFA,节点1所在的强连通分量编号,到节点1所在的强连通分量编号+N上的最长路,就是最后的答案。

大佬还提供了一份扩展用这样的思想,还可以解决一道这样的问题:给定一个有向图G,有m张优惠券,可以把一条边的边权改成一个固定值k,求节点s到节点t之间最短路的长度和方案数。方法是建立一个m+1层的多层图,层与层之间的边的权值都为k,然后跑最短路。由于用了优惠券不一定能达到优化的目的,所以答案为min(t,t+n,t+n+n,...)。方案数也可如此做。

在此感谢那位博主。https://www.luogu.org/blog/hsfzLZH1/solution-p3119

/*
 *looooop
 * Do not go gentle into that good night
 *				              -Dylan Thomas
 */

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stack>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <math.h>
#include <bitset>
#include <algorithm>
#include <climits>
using namespace std;

#define lson 2*i
#define rson 2*i+1
#define LS l,mid,lson
#define RS mid+1,r,rson
#define UP(i,x,y) for(i=x;i<=y;i++)
#define DOWN(i,x,y) for(i=x;i>=y;i--)
#define MEM(a,x) memset(a,x,sizeof(a))
#define W(a) while(a)
#define gcd(a,b) __gcd(a,b)
#define LL long long
#define N 1000005
#define MOD 1000000007
#define INF 0x3f3f3f3f
#define EXP 1e-8
#define lowbit(x) (x&-x)
#define MAX 200010
vector<int>G[MAX*2];
vector<int>re_G[MAX*2];
vector<int>DAG[MAX*2];
queue<int>q;
int dis[MAX];
int vis[MAX*2];
int dfn[MAX];
int low[MAX];
int color[MAX];
int belong[MAX];
int sum[MAX];
int n,m;
int tot;
int colorNum;
stack<int>St;
void init(){
    MEM(vis,0);
    MEM(dfn,0);
    MEM(low,0);
    MEM(dis,0);
    MEM(sum,0);
    MEM(belong,0);
}
void Tarjan(int st){
    dfn[st] = low[st] = ++tot;
    vis[st] = 1;
    St.push(st);
    for(int i = 0; i < G[st].size(); i++){
        int v = G[st][i];
        if(!dfn[v]){
            Tarjan(v);
            low[st] = min(low[st],low[v]);
        }
        else if(vis[v]){
            low[st] = min(low[st],dfn[v]);
        }
    }
    if(dfn[st] == low[st]){
        //int colorNum = 0;
        colorNum++;
        while(1){
            int now = St.top();
            sum[colorNum]++;
            belong[now] = colorNum;
            vis[now] = 0;
            St.pop();
            if(st == now)	break;
        }
    }
}

int main(int argc,char *argv[]){
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m ;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].push_back(v);
        //G[v].push_back(u);
        //re_G[v].push_back(u);
    }
    init();
    for(int i = 1; i <= n; i++){
        if(!dfn[i])	Tarjan(i);
    }
    for(int i = 1; i <= colorNum; i++){
        sum[colorNum+i] = sum[i];
    }
    for(int i = 1; i <= n; i++){
        for(int j = 0; j < G[i].size(); j++){
            int v = G[i][j];
            if(belong[i] != belong[v]){
                re_G[belong[i]].push_back(belong[v]);
                re_G[belong[v]].push_back(belong[i]+colorNum);
                re_G[belong[i]+colorNum].push_back(belong[v]+colorNum);
            }
        }
    }
	MEM(vis,0);
    vis[belong[1]] = 1;	q.push(belong[1]);
    while(!q.empty()){
        int x = q.front();
        for(int i = 0; i < re_G[x].size(); i++){
            int v = re_G[x][i];
            if(dis[v] < dis[x]+sum[x]){
                dis[v] = dis[x] + sum[x];
                if(!vis[v]){
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
        q.pop();
        vis[x] = 0;
    }
    printf("%d\n",dis[belong[1]+colorNum]);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值