Luogu3119[USACO15JAN]Grass Cownoisseur——Tarjan+图论建模

【题目描述】
约翰有n块草场,编号1到n,这些草场由若干条单行道相连。奶牛贝西是美味牧草的鉴赏家,她想到达尽可能多的草场去品尝牧草。

贝西总是从1号草场出发,最后回到1号草场。她想经过尽可能多的草场,贝西在通一个草场只吃一次草,所以一个草场可以经过多次。因为草场是单行道连接,这给贝西的品鉴工作带来了很大的不便,贝西想偷偷逆向行走一次,但最多只能有一次逆行。问,贝西最多能吃到多少个草场的牧草。
输入格式:

输入:

第一行:草场数n,道路数m。

以下m行,每行x和y表明有x到y的单向边,不会有重复的道路出现。

输出格式:

输出:

一个数,逆行一次最多可以走几个草场。

输入输出样例

输入样例#1:
7 10
1 2
3 1
2 5
2 4
3 7
3 5
3 6
6 5
7 2
4 7

输出样例#1:
6

说明:只要走1,2,4,7,2,5,3,1即可。


这道题要求我们求最大的强连通分量,但是我们可以走一次反向边,这点是这道题目的难点。
我们可以采用两种思路来做,接下来我都会介绍。首先无论如何,我们都要先用Tarjan缩点,将整张图缩成一个DAG。

思路1:
我们Tarjan之后就成为了一个DAG,我们在建新图的边时同时也将反向边建出来。我们将所有的强连通分量分成两类,一类是可以从1所在的强连通分量直接走到的,另一类是从1所在的强连通分量走反向边可以走到的。如果我们能从一个1类点走到另一个2类点。那么显然是可以的,所以我们枚举所有的1类点,并且枚举他们能够走到的所有2类点。然后更新答案即可。这个算法比较的暴力。
#include<bits/stdc++.h>
#include<vector>
#define MAXN 200005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,head[MAXN],nxt[MAXN],to[MAXN],cnt,dfn[MAXN],low[MAXN],sta[MAXN],vis[MAXN],top;
int ta,co,col[MAXN],rec,vi[MAXN],fl,can[MAXN],back[MAXN],q[MAXN],h,t,ans;
void add(int x,int y){
    to[cnt]=y;nxt[cnt]=head[x];head[x]=cnt;cnt++;
}
void tarjan(int x){
    dfn[x]=low[x]=++ta;
    vis[x]=1;sta[++top]=x;
    for(int i=head[x];i!=-1;i=nxt[i]){
        int go=to[i];
        if(!dfn[go]) tarjan(go),low[x]=min(low[x],low[go]);
        else if(vis[go]) low[x]=min(low[x],dfn[go]);
    }
    if(low[x]==dfn[x]){
        co++;int now=0;
        while(now!=x){
            now=sta[top--];
            if(now==1) fl=co;
            col[now]=co;
            vis[now]=0;
        }
    }
}
struct node{
    int sum;
    vector<int> f,g;
}F[MAXN];
int main()
{
    memset(head,-1,sizeof(head));
    n=read();m=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        add(x,y);
    }
    for(int i=1;i<=n;i++)
      if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++){
        F[col[i]].sum++;
        for(int j=head[i];j!=-1;j=nxt[j]){
            int go=to[j];
            if(col[go]!=col[i]) F[col[i]].f.push_back(col[go]),F[col[go]].g.push_back(col[i]);
        }
    }
    h=t=0;q[++t]=fl;
    while(h<t){
        int front=q[++h];
        for(int i=0;i<F[front].f.size();i++){
            int go=F[front].f[i];
            if(can[go]<can[front]+F[go].sum){
                q[++t]=go;
                can[go]=can[front]+F[go].sum;
            }
        }
    }
    h=t=0;q[++t]=fl;
    while(h<t){
        int front=q[++h];
        for(int i=0;i<F[front].g.size();i++){
            int go=F[front].g[i];
            if(back[go]<back[front]+F[go].sum){
                q[++t]=go;
                back[go]=back[front]+F[go].sum;
            }
        }
    }
    for(int i=1;i<=co;i++){
        if(!can[i]) continue;
        for(int j=0;j<F[i].g.size();j++){
            int to=F[i].g[j];
            if(back[to]||to==fl) ans=max(ans,can[i]+back[to]);
        }
    }
    for(int i=1;i<=co;i++){
        if(!back[i]) continue;
        for(int j=0;j<F[i].f.size();j++){
            int to=F[i].f[j];
            if(can[to]||to==fl) ans=max(ans,back[i]+can[to]);
        }
    }
    printf("%d",ans+F[fl].sum);
    return 0;
}

思路2:
我们可以用图论建模的思路。我们依旧缩点,缩出x个强连通分量。那么我们建出2x个点,其中1~x和x+1~2x分别按照原来的强连通分量关系连边(相当于图x+1~2x是图1~x的复制),然后我们从原图的指向点到新图的起始点连一条边,边权与原边相同,代表逆向走一条边。例如原图有一条边(i>–>j),则我们建一条(j>–>i+x)的边,表示反向边。而我们反向边又只能走一次,所以相当于当我们到图x+1~2x后,就不能回到1~x了。所以我们从点1所在的强连通分量开始SPFA,并处理好图的关系,最后的答案也就是dist[pl+x](pl表示1点所在的强连通分量)。
#include<bits/stdc++.h>
#include<vector>
#define MAXN 500005
using namespace std;
int read(){
    char c;int x;while(c=getchar(),c<'0'||c>'9');x=c-'0';
    while(c=getchar(),c>='0'&&c<='9') x=x*10+c-'0';return x;
}
int n,m,head[MAXN],nxt[MAXN],to[MAXN],cnt,dfn[MAXN],low[MAXN],sta[MAXN],vis[MAXN];
int co,ta,top,in[MAXN],q[MAXN*2],h,t,pre[MAXN],ans,fl,col[MAXN];
void add(int x,int y){
    to[cnt]=y;nxt[cnt]=head[x];head[x]=cnt;cnt++;
}
void tarjan(int x){
    low[x]=dfn[x]=++ta;
    vis[x]=1;sta[++top]=x;
    for(int i=head[x];i!=-1;i=nxt[i]){
        int go=to[i];
        if(!dfn[go]) tarjan(go),low[x]=min(low[x],low[go]);
        else if(vis[go]) low[x]=min(low[x],dfn[go]);
    }
    if(low[x]==dfn[x]){
        int now=0;co++;
        while(now!=x){
            now=sta[top--];
            if(now==1) fl=co;
            vis[now]=0;
            col[now]=co;
        }
    }
}
struct node{
    int sum;
    vector<int> f; 
}F[MAXN];
int main()
{
    memset(head,-1,sizeof(head));
    n=read();m=read();
    for(int i=1;i<=m;i++){
        int x=read(),y=read();
        add(x,y);
    }
    for(int i=1;i<=n;i++)
      if(!dfn[i]) tarjan(i);
    for(int i=1;i<=n;i++){
        F[col[i]].sum++;F[col[i]+co].sum++;
        for(int j=head[i];j!=-1;j=nxt[j]){
            int go=to[j];
            if(col[go]!=col[i]){
                F[col[i]].f.push_back(col[go]);in[col[go]]++;
                F[col[i]+co].f.push_back(col[go]+co);in[col[go]+co]++;
                F[col[go]].f.push_back(col[i]+co);in[col[i]+co]++;
            }
        }
    }
    h=t=0;q[++t]=fl;
    while(h<t){
        int front=q[++h];
        for(int i=0;i<F[front].f.size();i++){
            int go=F[front].f[i];
            if(pre[front]+F[go].sum>pre[go]&&!(front>co&&go<=co)){
                pre[go]=pre[front]+F[go].sum;
                q[++t]=go;
            }
        }
    }
    printf("%d",pre[fl+co]);
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值