P1262 间谍网络 java 洛谷 题解 tarjin算法

P1262 间谍网络 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)icon-default.png?t=M85Bhttps://www.luogu.com.cn/problem/P1262      这道题显然是使用强连通分量tarjin去做,只不过其中的细节处理部分比较难。首先用java写的话就只能用链式前向星的数组形式,用类去模仿c++的结构体会让代码运行很笨重,容易tle。

static int[] vv=new int[maxr],to=new int[maxr];        
static int[] he=new int[maxn];
static void addEdge(int u,int v){
    vv[++cnt]=v;
    to[cnt]=he[u];
    he[u]=cnt;
}

       tarjin模板大家用的一般都差不多,还有魔法快读StreamTokenizer这里就不细说了。

       因为java没有c++的struct,所以能用数组代替的地方我全用数组代替了,代码会显得复杂,但是数组用多了也就习惯了,而且数组更快,毕竟算法追求的就是快。

       tarjin处理后,我们会得到一个新的图,(这里一定要注意,这个新的图的每一个点都是之前图的强连通分量,那么只要这个分量中任何一个点能被收买,那这个分量就能被收买,花费是分量中能被收买的点的最小值)在这个新的图中,如果所有入度为0的点都能被收买,是不是就可以控制所有点了?那么问题关键就是看所有入度为0的点是否被boolean数组标记。如果有至少一个点不能被收买,那就不可能控制全部的间谍,我们需要输出最小不能收买的间谍的编号。这个好办,从所有可以收买的间谍开始深搜,再从1循环遍历,找到第一个没有搜到过的点就行了。如果所有点都能被收买,那我们把入度为0的点的的花费加起来,就是答案了。

        下面给详解源码:

import java.io.*;
import java.util.*;


public class Main {
    static int maxn=3001,maxp=20001,maxr=8001;
    static int n,p,r,cnt,top,num,len;
    //记录可以被收买的间谍的编号和花费
    static int[] arn=new int[maxp],arp=new int[maxp];
    //arr记录被收买间谍的花费,bribe记录间谍是否可以被收买,是上一行的另一种记录形式
    static int[] arr=new int[maxn];
    static boolean[] bribe = new boolean[maxn];
    //链式前向星用到的数组
    static int[] vv=new int[maxr],to=new int[maxr],he=new int[maxn];
    //tarjin算法用到的数组,stk模拟栈
    static int[] stk=new int[maxn],scc=new int[maxn];
    static int[] low=new int[maxn],dfn=new int[maxn];
    //ind记录入度,list记录新图中最小的贿赂费用
    static int[] ind=new int[maxn],list=new int[maxn];
    //eu[i]间谍掌握ev[i]间谍的证据
    static int[] eu=new int[maxr],ev=new int[maxr];
    //vis标记新图中可以贿赂的间谍,marked在求最小无法控制的间谍编号中 发挥作用(看后文理解)
    static boolean[] vis=new boolean[maxn],marked=new boolean[maxn];
    public static void main(String[] args) throws IOException {
        Arrays.fill(list,0x3f3f3f3f);
        n=nextInt();p=nextInt();
        //输入收买编号和收买费用
        for(int i=1;i<=p;i++) {
            arn[i]=nextInt();
            arp[i]=nextInt();
            arr[arn[i]]=arp[i];
            bribe[arn[i]]=true;
        }
        //读取r
        r=nextInt();
        for(int i=1;i<=r;i++){
            int u=nextInt(),v=nextInt();
            eu[i]=u;ev[i]=v;
            addEdge(u,v);
        }
        //tarjin
        for(int i=1;i<=n;i++)
            if(dfn[i]==0) tarjin(i);
        //重新初始化addEdge中用到的数组和静态变量
        Arrays.fill(vv,0);
        Arrays.fill(to,0);
        Arrays.fill(he,0);cnt=0;
        //重新创建一个链式前向星图
        for(int i=1;i<=r;i++){
            int u=eu[i],v=ev[i];
            //如果点u和点v不属于同一个强连通分量,则根据这两个强连通分量链式向前添加记录
            if(scc[u]!=scc[v]) {
                //入度 加一
                ind[scc[v]]++;
                addEdge(scc[u],scc[v]);
            }
        }
        //将新的强连通图中可以贿赂的点标记为true
        for(int i=1;i<=p;i++)
            vis[scc[arn[i]]]=true;
        //flag为true代表可以控制所有间谍,false代表无法控制所有间谍
        boolean flag=true;
        int ans=0;
        //len是新图的点的个数
        for(int i=1;i<=len;i++)
            if(ind[i]==0) {
                ans+=list[i];
                if(!vis[i])
                    //如果该点标记着false,意味着该点无法贿赂
                    flag=false;
            }
        if(flag) out.println("YES\n"+ans);
        else {
            //对所有点进行深搜
            for(int i=1;i<=p;i++) dfs(scc[arn[i]]);
            //找出未搜到的最小点,这个点就是编号最小的间谍编号
            for(int i=1;i<=n;i++)
                if(!marked[scc[i]]){
                    out.println("NO\n"+i);
                    break;
                }
        }
        //最后不要忘了刷新流,不然可能输出无效
        out.close();
    }
    static void dfs(int u){
        //常规深搜
        if(marked[u]) return;
        marked[u]=true;
        for(int i=he[u];i>0;i=to[i]){
            int v=vv[i];
            dfs(v);
        }
    }
    static void tarjin(int root){
        //常规tarjin,不多解释
        if(dfn[root]>0) return;
        dfn[root]=low[root]=++num;
        stk[++top]=root;
        for(int i=he[root];i>0;i=to[i]){
            int v=vv[i];
            if(dfn[v]==0){
                tarjin(v);
                low[root]=Math.min(low[root],low[v]);
            } else if(scc[v]==0){
                low[root]=Math.min(low[root],low[v]);
            }
        }
        if(dfn[root]==low[root]){
            len++;
            for(;;){
                int x=stk[top--];
                scc[x]=len;
                //如果该间谍可以贿赂
                if(bribe[x])
                    //求出改强连通分量最小值,即贿赂该分量里面所有间谍所需最小花费
                    list[len]=Math.min(list[len],arr[x]);
                if(x==root) break;
            }
        }
    }
    static void addEdge(int u,int v){
        vv[++cnt]=v;
        to[cnt]=he[u];
        he[u]=cnt;
    }
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
    public static int nextInt() throws IOException{
        in.nextToken();
        return (int)in.nval;
    }
    public static String nextString() throws IOException {
        in.nextToken();
        return in.sval;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玛卡左家陇分卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值