洛谷P2812/P2746解题思路

 题面

共有 n 所学校 (1≤n≤10000) 已知他们实现设计好的网络共 m 条线路,为了保证高速,网络是单向的。现在请你告诉他们至少选几所学校作为共享软件的母机,能使每所学校都可以用上。再告诉他们至少要添加几条线路能使任意一所学校作为母机都可以使别的学校使用上软件。

显然是一道强连通分量(因为是有向图)

先用链式向前星建图,然后 Tarjan 缩点

看看哪些学校内部可以共享软件

初步思路

统计有几个拓扑序入度为0的强连通分量,即为作为共享软件的母机数

我最初的错误思路:有几个拓扑序出度为0的强连通分量,即至少要添加几条线路(每次将出度为0的点往入度为0的点连)

(但是挂的很离谱,因为……你会看到的,具体可以看查缺补漏那里)

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
#define repe(i,u) for(int i = head[u];i;i = e[i].next)
const int N = 1e4 + 5;
struct edge{
    int v,next;
}e[N*N];
int dfn[N],low[N],stac[N],head[N],bel[N],rudu[N],chudu[N];
int n,x,tot,cnt,ans1,ans2,top,idx;
inline void add(int u,int v){
    e[++tot] = (edge){v,head[u]};
    head[u] = tot;
}
bool vis[N];
void tarjan(int u){
    low[u] = dfn[u] = ++cnt;
    stac[++top] = u;
    vis[u] = true;
    repe(i,u)
        if(!dfn[e[i].v]){
            tarjan(e[i].v);
            low[u] = min(low[u],low[e[i].v]);
        }
        else if(vis[e[i].v])
            low[u] = min(low[u],dfn[e[i].v]);
    if(low[u] == dfn[u]){
        int cur;
        idx++;
        do {
            cur = stac[top--];
            vis[cur] = false;
            bel[cur] = idx;
        } while(cur != u);
    }
}
int main(){
    ……//输入
    rep(i,n) if(!dfn[i]) tarjan(i);
    rep(i,n) repe(j,i)    if(bel[i] != bel[e[j].v])
            rudu[bel[e[j].v]]++,chudu[bel[i]]++;
    rep(i,idx) ans1 += rudu[i] == 0,ans2 += chudu[i] == 0;
    printf("%d\n%d",ans1,ans2);
    return 0;
}

代码交上去了,然后获得了——81分的低分。它 WA 了

查缺补漏

下载数据,发现这样一张图:

这是一个环(强连通分量)。出度为0的点虽然有,但是不需要加边呀

特判了有没有边连接的两端点所属的强连通分量不同,交上去72分……

//以上部分与之前一样
int main(){
    ……//输入
    rep(i,n) if(!dfn[i]) tarjan(i);
    rep(i,n) repe(j,i)    if(bel[i] != bel[e[j].v])
            rudu[bel[e[j].v]]++,chudu[bel[i]]++,ccnt = true;
    rep(i,idx) ans1 += rudu[i] == 0,ans2 += chudu[i] == 0;
    printf("%d\n%d",ans1,ccnt ? ans2 : 0);
    return 0;
}

有张图长这样

所以没有考虑不连通的点,实在不应该呢

所以又特判了一个地方

int main(){
    ……//以上没有变化
    rep(i,n) if(!dfn[i]) tarjan(i),num++;
    rep(i,n) repe(j,i)    if(bel[i] != bel[e[j].v])
            rudu[bel[e[j].v]]++,chudu[bel[i]]++,ccnt++;
    rep(i,idx) ans1 += rudu[i] == 0,ans2 += chudu[i] == 0;
    printf("%d\n%d",ans1,ccnt == 0 && num == 1 ? 0 : ans2);
    return 0;
}

已经很接近正确答案了!但是……90分

再看看~随手制造出一组 hack。

出度为0的点有2个,但是入度为0的点有3个……

要连至少3条边才能让它变成强连通分量啊!!!

其实这是最显然的一个错,对入度出度取 \max 即可

AC代码:

#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;i++)
#define repe(i,u) for(int i = head[u];i;i = e[i].next)
const int N = 1e4 + 5;
struct edge{
    int v,next;
}e[N*N];
int dfn[N],low[N],stac[N],head[N],bel[N],rudu[N],chudu[N];
int n,x,tot,cnt,ans1,ans2,top,idx,ccnt,num;
inline void add(int u,int v){
    e[++tot] = (edge){v,head[u]};
    head[u] = tot;
}
bool vis[N];
void tarjan(int u){
    low[u] = dfn[u] = ++cnt;
    stac[++top] = u;
    vis[u] = true;
    repe(i,u)
        if(!dfn[e[i].v]){
            tarjan(e[i].v);
            low[u] = min(low[u],low[e[i].v]);
        }
        else if(vis[e[i].v])
            low[u] = min(low[u],dfn[e[i].v]);
    if(low[u] == dfn[u]){
        int cur;
        idx++;
        do {
            cur = stac[top--];
            vis[cur] = false;
            bel[cur] = idx;
        } while(cur != u);
    }
}
int main(){
    scanf("%d",&n);
    rep(i,n){
        scanf("%d",&x);
        while(x){
            add(i,x);
            scanf("%d",&x);
        }
    }
    rep(i,n) if(!dfn[i]) tarjan(i),num++;
    rep(i,n) repe(j,i)    if(bel[i] != bel[e[j].v])
            rudu[bel[e[j].v]]++,chudu[bel[i]]++,ccnt++;
    rep(i,idx) ans1 += rudu[i] == 0,ans2 += chudu[i] == 0;
    printf("%d\n%d",ans1,ccnt == 0 && num == 1 ? 0 : max(ans1,ans2));
    return 0;
}

总结

先用 tarjan 缩点。

第一问答案是新图上入度为 0 的点数。

第二问答案是:

1.若新图上只有一个点,0

2.否则为新图上入度为 0 的点数与出度为 0 的点数取 \max

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值