P1262 间谍网络 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://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;
}
}