图论笔记(持续更新中~)

拓扑排序+链式前向星

*

因为若有环,则环内的点不会入队列被访问,所以可以判环 

O(n+m)


import java.io.*;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;



class Tx1{
	
	public static void main(String[] args) throws IOException{
		AReader input=new AReader();
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

		int n=input.nextInt();
		int m=input.nextInt();
		head=new int[n+1];
		e=new Edge[m<<1|1];
		cnt=0;
		int[] in=new int[n+1];
		for(int i=1;i<=m;++i) {
			int x=input.nextInt();
			int y=input.nextInt();
			addEdge(x, y, 0);
			in[y]++;
		}
		int tot=0;
		int[] turn=new int[n+1];
	    Queue<Integer> que=new LinkedList<Integer>();
	    
	    for(int i=1;i<=n;++i) {
	    	if(in[i]==0) {
	    		tot++;
	    		turn[tot]=i;
	    		que.add(i);
	    	}
	    }
	    while(!que.isEmpty()) {	
	    	int x=que.peek();
	    	que.poll();
	    	
	    	for(int i=head[x];i>0;i=e[i].nxt) {
	    		int t=e[i].to;
	    		in[t]--;
	    		if(in[t]==0) {
	    			tot++;
	    			turn[tot]=t;
	    			que.add(t);
	    		}
	    	}
	    }
		if(tot!=n) {
			out.println("-1");
		}else {
			for(int i=1;i<=n;++i) {
				out.print(turn[i]+" ");
			}
		}
		out.flush();
		out.close();
	}
	static
	class Edge{
		int fr;
		int to;
		int val;
		int nxt;//下一条边的位置
	}
	static int[] head;
	static Edge[] e;
	static int cnt;
	public static void addEdge(int fr,int to,int val) {
		cnt++;
		e[cnt]=new Edge();
		e[cnt].fr=fr;
		e[cnt].to=to;
		e[cnt].val=val;
		e[cnt].nxt=head[fr];//最后循环是倒着往前找边
		head[fr]=cnt;
	}
	static
    class AReader {
        private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        private StringTokenizer tokenizer = new StringTokenizer("");
        private String innerNextLine() {
            try {
                return reader.readLine();
            } catch (IOException ex) {
                return null;
            }
        }
        public boolean hasNext() {
            while (!tokenizer.hasMoreTokens()) {
                String nextLine = innerNextLine();
                if (nextLine == null) {
                    return false;
                }
                tokenizer = new StringTokenizer(nextLine);
            }
            return true;
        }
        public String nextLine() {
            tokenizer = new StringTokenizer("");
            return innerNextLine();
        }
        public String next() {
            hasNext();
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
 
        public long nextLong() {
            return Long.parseLong(next());
        }
 
    }
}


AOE网络图

*

  • 一个有向连通图,存在一个源点,一个汇点。
  • 每个边表示一项任务,边权即为完成该任务所需的时间,这条边指向的节点称作验收节点。
  • 每个验收节点,只有当它的所有入边的任务均完成,才能继续往下走。

 问:

        最终至少需要多长时间完成所有任务?

        哪些任务是影响总时间的关键?(即一旦延期则总时间增长)

解:

        先正向进行拓扑排序,每当一个点入度减一时,维护该点验收的最少时间t(即所有入边中累计边权最大的),同时记录相应的入边。最终汇点的t即为至少所需的时间。

        建立反向图。

        再反向进行拓扑排序,每当一个点入度减一时,维护该点进行下一项任务的最少时间T(即其所有入边节点的t减去入边的边权中最小的)

        最后对于每个节点,若t==T则说明该点验收完立马进行下一项任务,所以该点维护的入边即为关键任务。


最短路

       Dijkstra      

  • 求单源最短路。
  • 初始除出发点外,距离全为inf,出发点入小根堆,距离为0。
  • 小根堆按照距离排序。
  • 之后依次从堆中取出当前未访问且累计距离最近的点u,标记为访问过,更新与其相连的点v的距离,min \ dis_u+w_{(u,v)},dis_v,若更新则入堆。
  • 当堆为空时,最短路求解完毕。
  • 不能处理存在负边权
  • O((n+m)logm)




import java.io.*;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;



public class Main{
	
	public static void main(String[] args) throws IOException{
		AReader input=new AReader();
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

		int n=input.nextInt();
		int m=input.nextInt();
		int s=input.nextInt();
		cnt=0;
		head=new int[n+1];
		e=new Edge[m<<1|1];
		for(int i=1;i<=m;++i) {
			int u=input.nextInt();
			int v=input.nextInt();
			long w=input.nextLong();
			addEdge(u, v, w);
			
		}
		PriorityQueue<Node> que=new PriorityQueue<>
		((o1,o2)->{
			if(o1.dis-o2.dis==0L) {
				return 0;
			}else if(o1.dis-o2.dis<0L) {
				return -1;
			}else return 1;
		});
		long[] dis=new long[n+1];
		for(int i=1;i<=n;++i) {
			dis[i]=Long.MAX_VALUE;
		}
		boolean[] vis=new boolean[n+1];
		dis[s]=0;
		
		que.add(new Node(s, 0));
		while(!que.isEmpty()) {
			
			Node now=que.peek();
            que.poll();
			int u=now.id;
            if(vis[u])continue;

			long disu=now.dis;
			vis[u]=true;
			for(int i=head[u];i>0;i=e[i].nxt) {
				int v=e[i].to;
				long w=e[i].val;
				if(dis[v]>disu+w) {
					dis[v]=disu+w;
					que.add(new Node(v, dis[v]));
				}
			}
		}
		for(int i=1;i<=n;++i) {
			out.print(dis[i]+" ");
		}
		out.flush();
		out.close();
	}
	static
	class Node{
		int id;
		long dis;
		public Node(int I,long X) {
			id=I;
			dis=X;
		}
	}
	static
	class Edge{
		int fr;
		int to;
		long val;
		int nxt;//下一条边的位置
	}
	static int[] head;
	static Edge[] e;
	static int cnt;
	public static void addEdge(int fr,int to,long val) {
		cnt++;
		e[cnt]=new Edge();
		e[cnt].fr=fr;
		e[cnt].to=to;
		e[cnt].val=val;
		e[cnt].nxt=head[fr];//最后循环是倒着往前找边
		head[fr]=cnt;
	}
	static
    class AReader {
        private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        private StringTokenizer tokenizer = new StringTokenizer("");
        private String innerNextLine() {
            try {
                return reader.readLine();
            } catch (IOException ex) {
                return null;
            }
        }
        public boolean hasNext() {
            while (!tokenizer.hasMoreTokens()) {
                String nextLine = innerNextLine();
                if (nextLine == null) {
                    return false;
                }
                tokenizer = new StringTokenizer(nextLine);
            }
            return true;
        }
        public String nextLine() {
            tokenizer = new StringTokenizer("");
            return innerNextLine();
        }
        public String next() {
            hasNext();
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
 
        public long nextLong() {
            return Long.parseLong(next());
        }
 
    }
}

 SPFA

  • O(km)k为每点平均入队次数,最劣会到O(n*m),求无环最长路不如直接用Tuopu
  • 就是在O(n^3)的每轮对于每个点都进行扫描看能否对后继点进行更新,直到该轮无法进行更新的暴力上进行优化。
  • 由于可能会对一个不会更新后继的点进行重复访问,造成不必要的时间消耗。
  • 采用队列优化。
  • 若当前点u的距离disu没有更新,则对于其后继也不会有影响,所以不入队列。
  • disu更新,则其后继也要更新。
  • u在队列中,则把这次更新看作和入队那次更新为一轮更新,不入队列。
  • u不在队列中,则入队列,对后继进行更新。
  • 对于负权图,使用SPFA,对于正权图,使用Dijkstra
  • 若存在一点u,进入队列超过n次,即所有点都对它进行更新后,还能进行更新,则存在负环
package Tx;



import java.io.*;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;



class Tx1{
	
	public static void main(String[] args) throws IOException{
		AReader input=new AReader();
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));

		int n=input.nextInt();
		int m=input.nextInt();
		int s=input.nextInt();
		int t=input.nextInt();
		cnt=0;
		head=new int[n+1];
		e=new Edge[m<<1|1];
		for(int i=1;i<=m;++i) {
			int u=input.nextInt();
			int v=input.nextInt();
			long w=input.nextLong();
			addEdge(u, v, w);
			addEdge(v, u, w);//无向边
		}
		Queue<Integer> que=new LinkedList<Integer>();
		long[] dis=new long[n+1];
		boolean[] inque=new boolean[n+1];
		for(int i=1;i<=n;++i) {
			dis[i]=Long.MAX_VALUE;
		}
		dis[s]=0;
		que.add(s);
		inque[s]=true;
		while(!que.isEmpty()) {
			int u=que.peek();
			que.poll();
            inque[u]=false;//出队列
			for(int i=head[u];i>0;i=e[i].nxt) {
				int v=e[i].to;
				long w=e[i].val;
				if(dis[v]>dis[u]+w) {
					dis[v]=dis[u]+w;
					if(inque[v])continue;
					que.add(v);
                    inque[v]=true;
				}
			}
		}
		if(dis[t]==Long.MAX_VALUE){
            out.println("-1");
        }else{
            out.println(dis[t]);
        }
		out.flush();
		out.close();
	}
	static
	class Node{
		int id;
		long dis;
		public Node(int I,long X) {
			id=I;
			dis=X;
		}
	}
	static
	class Edge{
		int fr;
		int to;
		long val;
		int nxt;//下一条边的位置
	}
	static int[] head;
	static Edge[] e;
	static int cnt;
	public static void addEdge(int fr,int to,long val) {
		cnt++;
		e[cnt]=new Edge();
		e[cnt].fr=fr;
		e[cnt].to=to;
		e[cnt].val=val;
		e[cnt].nxt=head[fr];//最后循环是倒着往前找边
		head[fr]=cnt;
	}
	static
    class AReader {
        private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        private StringTokenizer tokenizer = new StringTokenizer("");
        private String innerNextLine() {
            try {
                return reader.readLine();
            } catch (IOException ex) {
                return null;
            }
        }
        public boolean hasNext() {
            while (!tokenizer.hasMoreTokens()) {
                String nextLine = innerNextLine();
                if (nextLine == null) {
                    return false;
                }
                tokenizer = new StringTokenizer(nextLine);
            }
            return true;
        }
        public String nextLine() {
            tokenizer = new StringTokenizer("");
            return innerNextLine();
        }
        public String next() {
            hasNext();
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
 
        public long nextLong() {
            return Long.parseLong(next());
        }
 
    }
}

 Floyed

  • 利用动态规划求解任意两点之间的最短路
  • f[i][j][k]表示从ij,只可能经过1\rightarrow k编号的点的最短路,i\neq j\neq k
  • 最外层枚举k,保证路径长度由小到大,则k可以在状态中省去
  • 邻接矩阵存图,直接相连的边对f初始化,即得出路径长度为1的f
  • O(n^3)
for(int k=1;i<=n;++i) {
    for(int i=1;i<=n;++i) {
        for(int j=1;j<=n;++j) {
            if((i!=j)&&(j!=k)&&(i!=k)) {
                if(f[i][j]>f[i][k]+f[k][j])
                    f[i][j]=f[i][k]+f[k][j];
            }
        }
    }
}

最小生成树

Prim

  • 从任意点出发,由点发散成树
  • vis记录已经在树上的点
  • 加边考虑由树上的点发出的最短的树枝,与Dij相似
  • 但不是到出发点的累计距离,而是相邻点的直接距离
  • 利用堆取出最小值为新加的边,然后接着扩展
  • Prim的边有向
import java.io.*;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.StringTokenizer;




public class Main{
	static int cnt=0;
	static Edge[] e;
	static int[] head;
	static
	class Edge{
		long val;
		int to;
		int fr;
		int nxt;
	}
	static void addEdge(int fr,int to,long val) {
		cnt++;
		e[cnt]=new Edge();
		e[cnt].fr=fr;
		e[cnt].to=to;
		e[cnt].val=val;
		e[cnt].nxt=head[fr];
		head[fr]=cnt;
	}
	static
	class Node{
		int x;
		long dis;
		public Node() {}
		public Node(int I,long D) {
			x=I;;
			dis=D;
		}
	}
	public static void main(String[] args) throws IOException{
		AReader input=new AReader();
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	    int n=input.nextInt();
	    int m=input.nextInt();
	    e=new Edge[2*m+1];
	    head=new int[n+1];
	    for(int i=1;i<=m;++i) {
	    	int a=input.nextInt();
	    	int b=input.nextInt();
	    	long val=input.nextLong();
	    	addEdge(a, b, val);
	    	addEdge(b, a, val);
	    }
	    PriorityQueue<Node> qu=new PriorityQueue<Node>((o1,o2)->{
	    	if(o1.dis-o2.dis<0)return -1;
	    	else if(o1.dis-o2.dis>0)return 1;
	    	else return 0;
	    });
	    boolean[] vis=new boolean[n+1];
	    long ans=0;
	    qu.add(new Node(1, 0));
	    while(!qu.isEmpty()) {
	    	Node now=qu.peek();
	    	qu.poll();
            int x=now.x;
            if(vis[x])continue;
	    	ans+=now.dis;
	    	vis[x]=true;
	    	for(int i=head[x];i>0;i=e[i].nxt) {
	    		int v=e[i].to;
	    		long disv=e[i].val;
	    		if(vis[v]==false) {
	    			qu.add(new Node(v,disv));
	    		}
	    	}
	    }
	    out.print(ans);
	    out.flush();
	    out.close();
	}
	
	static
    class AReader {
        private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        private StringTokenizer tokenizer = new StringTokenizer("");
        private String innerNextLine() {
            try {
                return reader.readLine();
            } catch (IOException ex) {
                return null;
            }
        }
        public boolean hasNext() {
            while (!tokenizer.hasMoreTokens()) {
                String nextLine = innerNextLine();
                if (nextLine == null) {
                    return false;
                }
                tokenizer = new StringTokenizer(nextLine);
            }
            return true;
        }
        public String nextLine() {
            tokenizer = new StringTokenizer("");
            return innerNextLine();
        }
        public String next() {
            hasNext();
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
 
        public long nextLong() {
            return Long.parseLong(next());
        }
 
    }
}

 Kruskal

  • Kruskal为无向边
  • 对所有的边由小到大进行排序,依次取边,进行点合并
  • 用并查集维护点合并,不在一个集合内的边才要
  • 共合并n-1
import java.io.*;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.StringTokenizer;




public class Main{
	static int cnt=0;
	static Edge[] e;
	static int[] head;
	static
	class Edge{
		long val;
		int to;
		int fr;
		int nxt;
	}
	static void addEdge(int fr,int to,long val) {
		cnt++;
		e[cnt]=new Edge();
		e[cnt].fr=fr;
		e[cnt].to=to;
		e[cnt].val=val;
		e[cnt].nxt=head[fr];
		head[fr]=cnt;
	}
	static int[] fa;
    static int[] size;
	static int find(int x) {
		if(x==fa[x])return x;
        else return fa[x]=find(fa[x]);
	}
    static void merge(int x,int y){
        if(size[x]>size[y]){
            fa[y]=x;
            size[x]++;
        }else{
            fa[x]=y;
            size[y]++;
        }
    }
	static
	class Node{
		int x;
		long dis;
		public Node() {}
		public Node(int I,long D) {
			x=I;;
			dis=D;
		}
	}
	public static void main(String[] args) throws IOException{
		AReader input=new AReader();
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	    int n=input.nextInt();
	    int m=input.nextInt();
	    e=new Edge[2*m+1];
	    fa=new int[n+1];
        size=new int[n+1];
	    head=new int[n+1];
	    for(int i=1;i<=m;++i) {
	    	int a=input.nextInt();
	    	int b=input.nextInt();
	    	long val=input.nextLong();
	    	cnt++;
            e[cnt]=new Edge();
            e[cnt].fr=a;
            e[cnt].to=b;
            e[cnt].val=val;
	    }
	    for(int i=1;i<=n;++i)fa[i]=i;
	    Arrays.sort(e,1,cnt+1,(o1,o2)->{
	    	if(o1.val-o2.val<0)return -1;
	    	else if(o1.val-o2.val>0)return 1;
	    	else return 0;
	    });
	    int t=0;
	    long ans=0;
	   for(int i=1;i<=cnt;++i) {
		   int u=e[i].fr;
		   int v=e[i].to;
		   int fau=find(u);
		   int fav=find(v);
		   if(fau==fav)continue;
            merge(fau,fav);
           ans+=e[i].val;
           t++;
           if(t==n-1)break;
	   }
	   out.print(ans);
	    out.flush();
	    out.close();
	}
	
	static
    class AReader {
        private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        private StringTokenizer tokenizer = new StringTokenizer("");
        private String innerNextLine() {
            try {
                return reader.readLine();
            } catch (IOException ex) {
                return null;
            }
        }
        public boolean hasNext() {
            while (!tokenizer.hasMoreTokens()) {
                String nextLine = innerNextLine();
                if (nextLine == null) {
                    return false;
                }
                tokenizer = new StringTokenizer(nextLine);
            }
            return true;
        }
        public String nextLine() {
            tokenizer = new StringTokenizer("");
            return innerNextLine();
        }
        public String next() {
            hasNext();
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
 
        public long nextLong() {
            return Long.parseLong(next());
        }
 
    }
}

唯一最小生成树

  • 通过删边得到唯一最小生成树,问最小代价
  • 修改Kruskal,排完序后,值相同的边会删去一些留下一些(值相同的边先后顺序无影响)
  • 对于集合内的添边删去

Forsaken喜欢独一无二的树


黑白染色

扩展并查集 

  • n个点,建立相应的对立点,扩展为2n,即i的对立点为2ii2i黑,i2i
  • i\leftrightarrow j,即i,j不同染色,所以i,2j同染色,放一起,2i,j同染色,放一起
  • 若没有取在相同集合点进行连边,则黑白染色成功

带权并查集 

  • i,j不同染色看作对i,j进行边权为1的连边,会产生连通图
  • 对于连通图上的点u,v,若u\rightarrow v距离为disdis\%2=0,则u,v相同染色,dis\%2=1则不同染色
  • 考虑并查集,将任意两点的关系,变为知道集合内点与最大祖先的关系,然后再进行比较得出关系
  • 考虑路径压缩维护关系传递,对于集合内的点,记录与直接父亲的关系即可,在find(fa)过程中得出与最大祖先的关系,此时由于路径压缩最大祖先也变为了直接父亲
  • re[x]=relation(fx,x) \\ \Rightarrow re[x]=(re[x]+re[fx])\%2,re[x]=relation(Fx,x)
  • 考虑合并,re[Fx]=(re[y]+relation(x,y)-re[x]+2)\%2,加2保证为正数,relation(x,y)实际不相连

例:食物链(A,B,C三染色) 

题目链接

  • 同理,若用扩展并查集,一个点相应有3个,扩展为3n,进行并查集维护
  • 同理,若用带权并查集,关系有relation(x,y)=0/1/2,模3进行维护扩展并查集 

扩展并查集 


import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;
import java.util.Vector;




public class Main{
	
	static int find(int x,int[] fa) {
		if(x==fa[x])return x;
		else return fa[x]=find(fa[x], fa);
	}
	
	
	
	public static void main(String[] args) throws IOException{
		AReader input=new AReader();
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	    int n=input.nextInt();
	    int k=input.nextInt();
	    int[] fa=new int[3*n+1];
	    for(int i=1;i<=3*n;++i)fa[i]=i;
	    int ans=0;
	    for(int i=1;i<=k;++i) {
	    	int op=input.nextInt();
	    	int x=input.nextInt();
	    	int y=input.nextInt();
	    	if(x>n||y>n) {
	    		ans++;
	    		continue;
	    	}
	    	if(op==1) {
	    		if(find(x, fa)==find(y+n, fa)||find(y, fa)==find(x+n, fa)) {
	    			//x吃y或y吃x
	    			//x吃y=>xA->yB,xB->yC,xC->yA
	    			//y吃x=>yA->xB,yB->xC->yC->xA
	    			ans++;
	    			continue;
	    		}
	    		fa[find(x, fa)]=find(y, fa);
	    		fa[find(x+n, fa)]=find(y+n, fa);
	    		fa[find(x+2*n, fa)]=find(y+2*n, fa);
	    	}else {
	    		if(x==y) {
	    			ans++;
	    			continue;
	    		}
	    		if(find(x, fa)==find(y, fa)||find(y, fa)==find(x+n, fa)) {
	    			//x和y是同类,y吃x
	    			ans++;
	    			continue;
	    		}
	    		fa[find(x, fa)]=find(y+n, fa);
	    		fa[find(x+n, fa)]=find(y+2*n, fa);
	    		fa[find(x+2*n, fa)]=find(y, fa);
	    	}
	    }
	    out.print(ans);
 	    out.flush();
	    out.close();
	}
	static
	class AReader{
	    BufferedReader bf;
	    StringTokenizer st;
	    BufferedWriter bw;

	    public AReader(){
	        bf=new BufferedReader(new InputStreamReader(System.in));
	        st=new StringTokenizer("");
	        bw=new BufferedWriter(new OutputStreamWriter(System.out));
	    }
	    public String nextLine() throws IOException{
	        return bf.readLine();
	    }
	    public String next() throws IOException{
	        while(!st.hasMoreTokens()){
	            st=new StringTokenizer(bf.readLine());
	        }
	        return st.nextToken();
	    }
	    public char nextChar() throws IOException{
	        //确定下一个token只有一个字符的时候再用
	        return next().charAt(0);
	    }
	    public int nextInt() throws IOException{
	        return Integer.parseInt(next());
	    }
	    public long nextLong() throws IOException{
	        return Long.parseLong(next());
	    }
	    public double nextDouble() throws IOException{
	        return Double.parseDouble(next());
	    }
	    public float nextFloat() throws IOException{
	        return Float.parseFloat(next());
	    }
	    public byte nextByte() throws IOException{
	        return Byte.parseByte(next());
	    }
	    public short nextShort() throws IOException{
	        return Short.parseShort(next());
	    }
	    public BigInteger nextBigInteger() throws IOException{
	        return new BigInteger(next());
	    }
	    public void println() throws IOException {
	        bw.newLine();
	    }
	    public void println(int[] arr) throws IOException{
	        for (int value : arr) {
	            bw.write(value + " ");
	        }
	        println();
	    }
	    public void println(int l, int r, int[] arr) throws IOException{
	        for (int i = l; i <= r; i ++) {
	            bw.write(arr[i] + " ");
	        }
	        println();
	    }
	    public void println(int a) throws IOException{
	        bw.write(String.valueOf(a));
	        bw.newLine();
	    }
	    public void print(int a) throws IOException{
	        bw.write(String.valueOf(a));
	    }
	    public void println(String a) throws IOException{
	        bw.write(a);
	        bw.newLine();
	    }
	    public void print(String a) throws IOException{
	        bw.write(a);
	    }
	    public void println(long a) throws IOException{
	        bw.write(String.valueOf(a));
	        bw.newLine();
	    }
	    public void print(long a) throws IOException{
	        bw.write(String.valueOf(a));
	    }
	    public void println(double a) throws IOException{
	        bw.write(String.valueOf(a));
	        bw.newLine();
	    }
	    public void print(double a) throws IOException{
	        bw.write(String.valueOf(a));
	    }
	    public void print(char a) throws IOException{
	        bw.write(String.valueOf(a));
	    }
	    public void println(char a) throws IOException{
	        bw.write(String.valueOf(a));
	        bw.newLine();
	    }
	}
}

带权并查集


import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;
import java.util.Vector;




public class Main{
	
	static int find(int x,int[] fa,int[] relation) {
		if(x==fa[x])return x;
		else {
			int f=fa[x];
			fa[x]=find(fa[x], fa, relation);
			relation[x]=(relation[x]+relation[f])%3;
			return fa[x];
		}
	}
	
	
	
	public static void main(String[] args) throws IOException{
		AReader input=new AReader();
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	    int n=input.nextInt();
	    int k=input.nextInt();
	    int ans=0;
	    int[] fa=new int[n+1];
	    int[] relation=new int[n+1];
	    for(int i=1;i<=n;++i) fa[i]=i;
	    for(int i=1;i<=k;++i) {
	    	int op=input.nextInt();
	    	int x=input.nextInt();
	    	int y=input.nextInt();
	    	if(x>n||y>n) {
	    		ans++;
	    		continue;
	    	}
	    	if(op==1) {
	    		int fx=find(x, fa, relation);
	    		int fy=find(y, fa, relation);
	    		if(fx==fy) {
	    			if(relation[x]!=relation[y]) {
	    				ans++;
	    				continue;
	    			}
	    		}else {
	    			fa[fx]=fy;
	    			relation[fx]=(relation[y]+0-relation[x]+3)%3;
	    		}
	    	}else {
	    		if(x==y) {
	    			ans++;
	    			continue;
	    		}
	    		int fx=find(x, fa, relation);
	    		int fy=find(y, fa, relation);
	    		if(fx==fy) {//0吃1,1吃2,2吃0
	    			if(relation[x]==0&&relation[y]==1||
	    					relation[x]==1&&relation[y]==2||
	    					relation[x]==2&&relation[y]==0)continue;
	    			ans++;
	    			continue;
	    		}else {
	    			fa[fx]=fy;
	    			relation[fx]=(relation[y]+2-relation[x]+3)%3;
	    		}
	    	}
	    }
	    out.println(ans);
 	    out.flush();
	    out.close();
	}
	static
	class AReader{
	    BufferedReader bf;
	    StringTokenizer st;
	    BufferedWriter bw;

	    public AReader(){
	        bf=new BufferedReader(new InputStreamReader(System.in));
	        st=new StringTokenizer("");
	        bw=new BufferedWriter(new OutputStreamWriter(System.out));
	    }
	    public String nextLine() throws IOException{
	        return bf.readLine();
	    }
	    public String next() throws IOException{
	        while(!st.hasMoreTokens()){
	            st=new StringTokenizer(bf.readLine());
	        }
	        return st.nextToken();
	    }
	    public char nextChar() throws IOException{
	        //确定下一个token只有一个字符的时候再用
	        return next().charAt(0);
	    }
	    public int nextInt() throws IOException{
	        return Integer.parseInt(next());
	    }
	    public long nextLong() throws IOException{
	        return Long.parseLong(next());
	    }
	    public double nextDouble() throws IOException{
	        return Double.parseDouble(next());
	    }
	    public float nextFloat() throws IOException{
	        return Float.parseFloat(next());
	    }
	    public byte nextByte() throws IOException{
	        return Byte.parseByte(next());
	    }
	    public short nextShort() throws IOException{
	        return Short.parseShort(next());
	    }
	    public BigInteger nextBigInteger() throws IOException{
	        return new BigInteger(next());
	    }
	    public void println() throws IOException {
	        bw.newLine();
	    }
	    public void println(int[] arr) throws IOException{
	        for (int value : arr) {
	            bw.write(value + " ");
	        }
	        println();
	    }
	    public void println(int l, int r, int[] arr) throws IOException{
	        for (int i = l; i <= r; i ++) {
	            bw.write(arr[i] + " ");
	        }
	        println();
	    }
	    public void println(int a) throws IOException{
	        bw.write(String.valueOf(a));
	        bw.newLine();
	    }
	    public void print(int a) throws IOException{
	        bw.write(String.valueOf(a));
	    }
	    public void println(String a) throws IOException{
	        bw.write(a);
	        bw.newLine();
	    }
	    public void print(String a) throws IOException{
	        bw.write(a);
	    }
	    public void println(long a) throws IOException{
	        bw.write(String.valueOf(a));
	        bw.newLine();
	    }
	    public void print(long a) throws IOException{
	        bw.write(String.valueOf(a));
	    }
	    public void println(double a) throws IOException{
	        bw.write(String.valueOf(a));
	        bw.newLine();
	    }
	    public void print(double a) throws IOException{
	        bw.write(String.valueOf(a));
	    }
	    public void print(char a) throws IOException{
	        bw.write(String.valueOf(a));
	    }
	    public void println(char a) throws IOException{
	        bw.write(String.valueOf(a));
	        bw.newLine();
	    }
	}
}

分层图

路径决策

    相对于普通最短路,其每个点多了状态设置,即点自己本身有不同的状态,对应其连边有不同的边权。

    通过按状态拆点添新边(不进行决策可以直接跑),建整合点(减少建边复杂程度,压缩路径)

    状态有:当前点的颜色,当前点的操作数,棋盘上当前点的上下左右方向

卯酉东海道

小雨坐地铁

P1948 [USACO08JAN] Telephone Lines S

G. Rudolf and Subway

P2937 [USACO09JAN] Laserphones S


最大生成树

*

  • 先找出树的直径,然后按其他点到直径端点最远的距离添边
  • 找出树的直径只需找出一个非叶子节点,找出距离最远的两个点即为直径端点

Highway

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值