P1653 [USACO04DEC] Cow Ski Area G 题解

· · 题解

题目大意

在一张图中,对于一个点,有多条出边,指向与它相连的并且海拔小于等于它的点。求最少要添加多少条边使得整个图强联通。

思路

Tarjan 缩点。强连通图的概念是既可以通过有向边从 A 到 B,也可以通过有向边从 B 到 A,所以我们可以记录只有出边和只有入边的点取最大值(贪心思想,将这两种点两两配对)。

Code

//P1653 [USACO04DEC] Cow Ski Area G 
#include<bits/stdc++.h>

#define reg register

using namespace std;

inline int read() {
    int x=0,f=1;char s=getchar();
    while (s>'9'||s<'0') {
        if (s=='-') f=-f;
        s=getchar();
    }
    while (s>='0'&&s<='9') {
        x=x*10+s-'0';
        s=getchar();
    }
    return x*f;
}

const int N = 550;
const int xx[4]={-1,1,0,0};
const int yy[4]={0,0,-1,1};

int n,m,mmap[N][N],first[N*N],cnt,low[N*N],dfn[N*N],belong[N*N],in[N*N],out[N*N],tot,now;
int ans1,ans2;  //ans1 记录只有出边的点,ans2 记录只有入边的点 
stack<int>s;
bool instack[N*N];
struct edge{
    int to,nxt;
}edges[N*N*4];

int C(int i,int j) { return (i-1)*m+j; }  //将图中的坐标化成点,方便表示 

void add(int u,int v) {  //构图 
    edges[++cnt].to=v;
    edges[cnt].nxt=first[u];
    first[u]=cnt;
}

void dfs(int root) {  //缩点ing 
    dfn[root]=low[root]=++now;
    s.push(root);instack[root]=true;
    for (reg int t=first[root];t;t=edges[t].nxt) {
        int h=edges[t].to;
        if (!dfn[h]) dfs(h),low[root]=min(low[root],low[h]);
        else if (instack[h]) low[root]=min(low[root],low[h]);
    }
    if (low[root]==dfn[root]) {  //该点是强连通分量的源点(或者孤点) 
        tot++;
        while (1) {
            int h=s.top();s.pop();
            belong[h]=tot;instack[h]=false;
            if (h==root) break;
        }
    }
}

int main(){
    m=read();n=read();
    for (reg int i=1;i<=n;++i) {
        for (reg int j=1;j<=m;++j) mmap[i][j]=read();  
    }
    for (reg int i=1;i<=n;++i) {
        for (reg int j=1;j<=m;++j) {
            for (reg int f=0;f<4;++f) { 
                int to_x=i+xx[f],to_y=j+yy[f];
                if (to_x>=1&&to_x<=n&&to_y>=1&&to_y<=m&&mmap[i][j]>=mmap[to_x][to_y]) //满足条件,建边 
                    add(C(i,j),C(to_x,to_y));
            }
        }
    }
    for (reg int i=1;i<=n;++i) 
        for (reg int j=1;j<=m;++j) if (!dfn[C(i,j)]) dfs(C(i,j));  //防止有节点没有遍历到 
    for (reg int i=1;i<=n;++i) {
        for (reg int j=1;j<=m;++j) {
            int root=C(i,j);
            for (reg int t=first[root];t;t=edges[t].nxt) {
                int h=edges[t].to;
                if (belong[root]==belong[h]) continue;  //这条边属于一个强连通分量里面的内边 
                out[belong[root]]++;in[belong[h]]++;  //记录缩点之后每个节点的入度和出度 
            }
        }
    }
    if (tot==1) {   //特判 
        printf("0\n");
        return 0;
    }
    for (reg int i=1;i<=tot;++i) {  //统计数量 
        if (!in[i]) ans1++;
        if (!out[i]) ans2++; 
    }
    printf("%d\n",max(ans1,ans2));  //输出最大值 
    return 0;
}
//end