【备战蓝桥杯系列】蓝桥杯国二选手笔记二:算法模版笔记(Java)

感谢大家的点赞,关注,评论。准备蓝桥杯的同学可以关注一下本专栏哦,不定期更新蓝桥杯笔记以及经验分享。本人多次参加过蓝桥杯,并获得过蓝桥杯国二的成绩。

算法模版笔记(Java)

这篇文章给大家分享我的蓝桥杯算法模版(Java版本)

快读

下面这个板子才是真正有用的(在蓝桥杯有用)

  • 读入数字:用StreamTokenizer,先nextToken,再st.nval拿到值
  • 读字符串:用BufferedReader,用readLine读。
  • 如果数字和字符混读,那么在每一个数字读完再读String的时候要多用一个nextLine读入回车符
class Read{
	StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
	BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
	public int nextInt() throws Exception{
		st.nextToken();
		return (int) st.nval;
	}
	
	public double nextDouble() throws Exception{
		st.nextToken();
		return st.nval;
	}
	
	public long nextLong() throws Exception{
		st.nextToken();
		return (long)st.nval;
	}
	
	public String nextLine() throws Exception{
		return reader.readLine();
	}
}

在蓝桥杯中,1e5数据量的输入需要用快读才能拿下更多的分

快写

println很慢,执行1e5次println需要1秒,执行1e6次print需要1秒,所以当输出超过1e6就要用快写

PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
//......
out.flush();

邻接表

static int N = 110000 , M = 2*N;
static int[] h = new int[N],e = new int[M],ne = new int[M],w = new int[M];
static void add(int a,int b,int c) {
    e[idx] = b;
    w[idx] = c;
    ne[idx] = h[a];
    h[a] = idx++;
}
static void init() {
    Arrays.fill(h, -1);
}

取模

处理负数的模是负数的问题,可以让负数的模是正数

	static int getMod(int a,int mod) {
		return (a%mod + mod)%mod;
	}

快速幂

	static long qpow(long a,long b) {
		long res = 1;
		while(b>0) {
			if((b&1)==1) res = res*a%Mod;
			b>>=1;
			a = a*a%Mod;
		}
		return res;
	}
//ps:用long来存,里面有乘法,会爆int的,即使题目里面的输入是int的。

模拟

日期

static int[] d = {0,31,30,31,30,31,30,31,31,30,31,30,31};

static int nextDay(int n) {
	int year = n/10000;
	int mon = n%10000/100;
	int day = n%100;
	
	//判断闰年,确定2月有多少天
	if(year%4==0 && year%100!=0 || year%400==0) 
		d[2] = 29;
	else d[2] = 28;
		
	//计算
	day++;
	if(day>d[mon]) {
		mon++;
		day = 1;
	}
	if(mon>12) {
		mon = 1;
		year++;
	}
	return year*10000+mon*100+day;
}

//从开始日期枚举到结束日期
for(int i=from;i<=to;i=nextDay(i)) {
    if(check(i))
        ans++;
}

数据结构

前缀和

前缀和和差分数组都是从下标1开始

//初始化
sum[i] = sum[i-1] + arr[i];

//求和arr的[a,b]的和
sum[b] - sum[a-1];
二维前缀和
	//1、初始化
	for(int i=1;i<=n;i++) 
   	 	for(int j=1;j<=m;j++) {
        	s[i][j] = sc.nextInt();
        	s[i][j] += s[i-1][j]+s[i][j-1]-s[i-1][j-1];
    	}
    
   //2.求和:求(x1,y1)和(x2,y2)中间的子矩形的和
   System.out.println(s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]);

差分

前缀和的逆运算

作用:能够以O(1)的时间修改一个区间的值

原数组sum,差分数组chafen

//初始化
for(int i = 1;i<=n;i++) {
    arr[i] = sc.nextInt();
    chafen[i] = arr[i] - arr[i-1];
}

//操作一:对于原数组sum中的[l,r]中的值+v(这是离线操作,实际上sum没有被更新)
static void add(int l,int r,int v){
    chafen[l]+=v;
    chafen[r+1]-=v;
}

//操作二:计算最终结果(数组的最终值)
for(int i=1;i<=n;i++) 
    sum[i] = sum[i-1] + chafen[i];

//差分原理
chafen[i] = sum[i] - sum[i-1]
    
//最终数组的原理(修改结束后)
sum[i] = chafen[1] + chafen[2] + ... + chafen[i]

  • 思考
    • 差分数组第一个元素是a,后面元素全0:代表原数组的值都是a
    • 特殊的,差分数组中元素全0:代表原数组全0
    • chafen[i]会对sum[i+3]的值影响,要想把sum[i+3]降到0,就要先把chafen[i]降成0
二维差分
	原理:和二维前缀和一样的公式
	
	1.初始化:
	for(int i=1;i<=n;i++) 
        for(int j=1;j<=m;j++) {
            s[i][j] = sc.nextInt();
            chafen[i][j] = s[i][j] - s[i-1][j] - s[i][j-1] + s[i-1][j-1];
    }
    
    2.在(x1,y1)和(x2,y2)中间的子矩形中的所有元素都加c
                chafen[x1][y1] += c;
                chafen[x1][y2+1] -= c;
                chafen[x2+1][y1] -= c;
                chafen[x2+1][y2+1] += c;
                
    3、计算最终数组
    for(int i=1;i<=n;i++) {
        for(int j=1;j<=m;j++) {
            s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + chafen[i][j];
            System.out.print(s[i][j]+" ");
        }
        System.out.println();
    }

离散化

  • 如果一个数组,他的值域的范围很大,比如上界减下界的值大于1e8,但是数组的长度只有1e5,通常需要使用离散化,这类题目需要用把数组转化成只保留相对关系的离散化数组。

  • 比如,arr = {33,88,44,111},可以转换成:w = {1,3,2,4};

  • 方法就是对arr排序,并保留下标,最后把下标位置上的数换成排名.

  • 步骤

    • 1.排序
    • 2.去重
    • 3.二分寻找
  • 严谨模版

    	//计算离散化数组O(nlogn)
    	static Integer[] solve(int[] t,int sz) {
    		TreeSet<Integer> se = new TreeSet<Integer>();
    		for(int i=1;i<=sz;i++)
    			se.add(t[i]);
            Integer[] order = se.toArray(new Integer[0]);
     		return order;
    	}
    
    	//二分找离散后的值
    	static int find(int x){
    	    int l = 0,r = idx-1;
    	    while(l<r){
    	        int mid = l+r+1>>1;
    	        if(order[mid]<=x) l = mid;
    	        else r = mid-1;
    	    }
    	    return l;
    	}
    
    	//二分,或者用arr[i]的时候查一下离散值
    		find(a[i]);
    
  • 不严谨模版模板(适用于所有数都不同的情况)

   //初始化结构体数组
    Node[] arr = new Node[N];
    for(int i=1;i<=n;i++){
        arr[i] = new Node(nums[i],i);
    }
    //排序
    Arrays.sort(arr,1,n+1,(a,b)->a.val-b.val);
    //用排名代替值
    for(int i=1;i<=n;i++){
        w[arr[i].id] = i;
    }

    class Node{
        int val,id;//id是下标位置
        Node(int val,int id){
            this.val = val;
            this.id = id;
        }
    }
前缀和离散化

AcWing 802区间和:https://www.acwing.com/activity/content/code/content/6437889/

求区间和。

用treemap

  • 本题就是前缀和,唯一的不同是数据范围,数组的下标是-1e9~1e9,不能用连续的数组去存储,需要离散化存储,java中用treemap作为离散化的前缀和数组,需要对下标排序,从而计算前缀和。

  • 计算ketSet拿到所有的key,从而计算前一个位置的map值,

  • 此外,所有的查询下标l,r也需要初始化在map中,否则就无法计算。这样map的最大值就要开到3e5,keys数组要这么大

  • 查询的时候,需要返回l前一个key的值,而不是简单的l-1,所以需要用到treemap的api:lowerEntry(key),得到小于key的最大的key对应的实体

		//输入
		for(int i=1;i<=n;i++) {
            int idx = sc.nextInt();
            int val = sc.nextInt();
            mp.put(idx,mp.getOrDefault(idx, 0)+val);
        }
		//查询的区间初始化到map中
        for(int i=0;i<m;i++) {
            int l = sc.nextInt();
            int r = sc.nextInt();
            q[i][0] = l;q[i][1] = r;
            mp.put(l, mp.getOrDefault(l, 0));
            mp.put(r, mp.getOrDefault(r, 0));
        }
        //拿到所有用到的下标
        Integer[] keys =  mp.keySet().toArray(new Integer[1]);
        for(int i=1;i<keys.length;i++) {
            mp.put(keys[i],mp.get(keys[i])+mp.get(keys[i-1]));//前缀和
        }

        for(int i=0;i<m;i++) {
            int l = q[i][0];
            int r = q[i][1];
            if(l==keys[0])
//              System.out.println(mp.ceilingEntry(r).getValue());//可以这么写
                System.out.println(mp.get(r));
            else
//              System.out.println(mp.get(r)-mp.get(l-1));//不能这么写!!!
                System.out.println(mp.get(r)-mp.lowerEntry(l).getValue());

        }

字典树Tire

作用:高效的进行字符串存储和查询(也可以用来存二进制,参考题:最大异或对)

  • 一棵26叉树,每个节点的二十六个孩子分别代表是否存在,存在就是>0的数,不存在就是0
  • 根节点是0,如果son数组的值是0表示这个点不存在,每一个新的点都是 >= 1。
  • N是所有字符串的长度和(s1.len + s2.len + …)的最大值
  • 时间复杂度O(字符串的最大长度)
        static int N = 101000;//所有字符串的长度和(s1.len + s2.len + ...)的最大值
		static int[][] son = new int[N][26];
		//son[i][k] = j表示:节点i的下一个位置存放的字符是k的节点索引是j
		static int[] cnt = new int[N];
		static int idx = 0;
		
		//插入一个字符串
		static void insert(char[] str) {
			int p=0;//字典树指针,初始时指向根节点0
			for(int i=0;i<str.length;i++) {
				int u = str[i]-'a';
				if(son[p][u]==0) //如果是0,就代表这个节点不存在,那么创建一个
                    son[p][u]= ++idx;
				p = son[p][u];
			}
			cnt[p]++;
		}
		
		//查询字符串str出现的次数
		static int query(char[] str) {
			int p = 0;
			for(int i=0;i<str.length;i++) {
				int u = str[i]-'a';
				if(son[p][u]==0) return 0;
				p = son[p][u];
			}
			return cnt[p];
		}
		参考:https://www.acwing.com/activity/content/code/content/725149/

并查集

	//存放父节点(祖先节点)
	static int[] p = new int[N];
	//找到x的祖先节点(路径压缩)
	static int find(int x) {
        if(x==p[x]) return x;
        return p[x] = find(p[x]);
    }

	//初始化
	for(int i=1;i<=n;i++) 
        p[i]=i;//每个节点都是根节点
	//操作1:合并
	p[find(a)]=find(b);
	//操作2:查询
	if(find(a)==find(b))//在同一个集合中
        

树状数组

两个操作(一维数组)

  • 单点修改:修改某个位置上的数的值:O(logn)
  • 区间求和:求一个区间前缀和:O(logn)

树状数组:

C[x] = sum(x-lowbit(x) , x] #左开右闭

普通前缀和虽然是O(1)的,但是一旦修改了某一个元素后,计算前缀和的时间复杂度就是O(n)了

普通前缀和是离线的,树状数组是在线的。(树状数组是根据下标的二进制后面有几个0进行划分层数的,比如3的二进制有0个0,那么就在最底层)

​ 添加元素 求和

普通前缀和: O(1) O(n)

树状数组: O(logn) O(logn)

模板

  • 树状数组下标从1开始的
	//原数组:arr,树状数组:tree,=
 	static int lowbit(int x) {
		return x&-x;
	}
	//在arr[idx]的值添加v
	static void add(int idx,int v) {
        //arr[idx] += v;//如果将idx变成v
		for(int i=idx;i<=n;i+=lowbit(i)) 
			tree[i]+=v;
	}
	//计算arr[1~x]的和
	static int query(int idx) {
		int res = 0;
		for(int i=idx;i>0;i-=lowbit(i)) 
			res += tree[i];
		return res;
	}

	static int query(int l,int r){
        return sum(r)-sum(l-1);
    }

	//初始化,如果初始是0不需要初始化
	for(int i=1;i<=n;i++)
		add(i,arr[i]);

	//arr[idx]的值添加y
	add(idx,y);

	//计算[x,y]前缀和
	query(x,y)

	//arr[idx]修改成y
    add(idx,y-arr[idx]);
权值树状数组
  • 树状数组下标是值域(计算左边小于它的数的个数)

  • 关键信息:树状数组的query(x)操作是计算一个前缀和,如果下标是数组中的元素值,那么刚好就可以logn得到个数。

  • 思路:对于每个arr[i],每次需要求出左边/右边比它小的数的个数,由于数组长度的限制,而实际数组的值域很大,所以这种思路一般需要进行离散化处理,这样得到的数组的值域就是1<=y<=n,这样就能把值域作为树状数组的下标了。对于树状数组的每一次query,实际上是计算一个前缀和,比如query(x),返回值就是1~x中小于等于x的数的个数。

  • 离散化数组w,保存的相对大小关系,w中存储的数是1~n,这个值域就是作为树状数组的下标

  • 此时的tree[i]=j记录的是值为i的数的个数,每一次query(idx),实际就是找到小于等于idx的数的个数是j(而idx在这里正好是值)

思路参考:https://zhuanlan.zhihu.com/p/93795692

例题:lc315. 计算右侧小于当前元素的个数(树状数组+离散化)

应用

  • lc315. 计算右侧小于当前元素的个数(树状数组+离散化)
  • 计算逆序对的数量(可以用归并排序、也可以用树状数组):倒着求
  • AcWing 241. 楼兰图腾
  • AcWing 1215. 小朋友排队(结论+树状数组)
差分树状数组

树状数组的变形

  • 区间修改、单点查询

    • 思路:在树状数组上做差分

      • 每一次修改[l,r]元素+d,相当于add(l,d) ,add(r+1,-d)
      • 每一次查询x处元素的值,相当于sum(x)
      • 差分树状数组的初始化
              for(int i=1;i<=n;i++) {
      			arr[i] = sc.nextInt();
      			add(i,(int)(arr[i]-arr[i-1]));
      		}
      
  • 区间修改、区间查询

    • 思路:推公式

      备注 2020年7月25日.png

树状数组维护前缀最大值

树状数组其实也能维护最大值,但是只能求前缀的最大值,不能求子区间的最大值(因为最大值没有减法)

//插入一个数,更新最大值
static void solveMax(int idx,long val) {
    for(int i=idx;i<N;i+=lowbit(i))
        tree[i]= Math.max(tree[i],val);
}

//计算1~idx中的最大值
static long maxPre(int idx) {
    long res = 0;
    for(int i=idx;i>0;i-=lowbit(i))
        res = Math.max(res, tree[i]);
    return res;
}

https://www.acwing.com/activity/content/code/content/6538977/

线段树

树状数组的扩展,应用场景远大于树状数组

  • 单点修改、区间求和的板子如下
	static int[] w = new int[N];//原数组
	static Node[] tree = new Node[4*N];//线段树数组

	//向上更新,根节点是u
	static void pushup(int u) {
		tree[u].val = tree[u<<1].val + tree[u<<1|1].val;
	}

	//构建线段树函数,根节点u,构建区间[l,r]
	static void build(int u,int l,int r) {
		//叶子节点直接记录权值
		if(l==r) 
			tree[u] = new Node(l, r, w[l]);
		else {
			tree[u] = new Node(l, r, 0);//值先记为0,pushup再更新
			int mid = l+r>>1;
			build(u<<1, l, mid);//构建左子树
			build(u<<1|1, mid+1, r);//构建右子树
			pushup(u);//向上更新val
		}
	}
	
	//计算当前节点u下的在[l,r]范围内的和,u代表当前节点(递归的时候l,r是不变的)
	static int query(int u,int l,int r) {
		//如果区间和val完全包含当前节点,就直接加上
		if(tree[u].l>=l && tree[u].r<=r) 
			return tree[u].val;
		//否则,当前节点的范围一分为二
		int mid = tree[u].l + tree[u].r >>1;
		int sum = 0;//这个参数根据具体题目变化,如果是求和这里就是sum
		//如果左、右区间满足就加上
		if(l <= mid) sum += query(u<<1,l,r);
		if(r >= mid+1) sum += query(u<<1|1, l, r);
		return sum;
	}
	
	//单点修改:给idx位置上的数+v。u代表当前节点
	static void modify(int u,int idx,int v) {
		//遍历到叶子节点就是具体的位置idx
		if(tree[u].l==tree[u].r)
			tree[u].val += v;
		else {
			//判断是在该节点u的左子树还是右子树上
			int mid = tree[u].l + tree[u].r >>1;
			if(idx<=mid) modify(u<<1, idx, v);
			else modify(u<<1|1, idx, v);
			pushup(u);//向上更新,这里非常容易忘记!!!
		}
	}

    static class Node{
        int l,r,val;
        public Node(int l,int r,int val) {
            this.l = l;
            this.r = r;
            this.val = val;
        }
    }

//初始化:
build(1,1,n);

//查询[a,b]的结果:
query(1,a,b);

//修改idx位置的值+v
modify(1,idx,v);

细节:

  • 线段树数组tree是4倍N的区间(N是原数组最大长度)
  • pushup是用子节点的信息更新父节点的信息
  • build是初始化函数
  • query是查询函数(最容易错),参数l和r在递归的时候都是不变的;mid是取树的左右边界的中点而不是l和r的中点;递归的出口是当前节点已经被完全包含就返回val,不是遍历到叶子节点。
  • modify函数的mid也是计算树节点的中点,而不是l和r的中点;递归调用左右子树后一定要pushup
  • 三个核心函数build,query,modify都是递归调用;只有quey有返回值;build、modify都需要pushup
带懒标记的线段树
  • 应用场景:区间修改问题

  • 描述:如果当修改的不是一个点而是一个区间的时候,使用懒标记能够依然以O(logn)的时间进行。

  • 思路:修改[l,r]区间内的数,每次递归遍历到一个节点区间[a,b],当这个节点的区间[a,b]被[l,r]完全覆盖的时候,为了节约性能,我们直接在这个节点上加上懒标记(相当于记上这笔账),并更新val,然后直接返回。如果后面在进行修改/查询的时候,先进行PushDown,把父节点的账给孩子们算清楚,再去对孩子们进行操作

  • 核心操作:

    	//向下更新:用父节点的状态更新子节点,把懒标记给儿子节点
    	//把父亲的账给儿子算清
    	static void pushDown(int u) {
    	    //传递懒标记
    		tree[u<<1].add += tree[u].add;
    		tree[u<<1|1].add += tree[u].add;
    		//传递最大值
    		tree[u<<1].val += tree[u].add;
    		tree[u<<1|1].val += tree[u].add;
    		tree[u].add = 0;//清空父节点的标记
    	}
    	    //区间修改:将[l,r]的值都+val
    	static void modify(int u,int l,int r,int val) {
    	    //如果这个节点的区间被完全覆盖,就加上懒标记
    		if(tree[u].l>=l && tree[u].r<=r) {
    			tree[u].add += val;
    			tree[u].val += val;
    		}else {//否则,不被完全覆盖,先把账算清再修改区间,最后更新到父节点
    			pushDown(u);
    			int mid = tree[u].l+tree[u].r>>1;
    			if(mid>=l) modify(u<<1,l,r,val);
    			if(mid+1<=r) modify(u<<1|1,l,r,val);
    			pushUp(u);
    		}
    	}
    	
    	//区间求和
    	static long query(int u,int l,int r) {
    		if(tree[u].l>=l && tree[u].r<=r)
    			return tree[u].val;
    		//先算清账,再求内部的值
    		pushDown(u);
    		long mx = INF;
    		int mid = tree[u].l+tree[u].r>>1;
    		if(l<=mid) mx = Math.min(mx,query(u<<1,l,r));
    		if(mid+1<=r) mx = Math.min(mx,query(u<<1|1,l,r));
    		return mx;
    	}
    
lazy线段树模版维护:最大值

完整代码:

    class Node{
        int l,r;
        long val;
        long add;//懒标记
        public Node(int l,int r,long val,long add) {
            this.l = l;this.r = r;this.val = val;this.add = add;
        }
    }
	static int[] w = new int[N];
	static Node[] tree = new Node[4*N];
	//向上更新:用子节点的值更新父节点
	static void pushUp(int u) {
		tree[u].val = Math.min(tree[u<<1].val,tree[u<<1|1].val);
	}
	//向下更新:用父节点的状态更新子节点,把懒标记给儿子节点
	//把父亲的账给儿子算清
	static void pushDown(int u) {
	    //传递懒标记
		tree[u<<1].add += tree[u].add;
		tree[u<<1|1].add += tree[u].add;
		//传递最大值
		tree[u<<1].val += tree[u].add;
		tree[u<<1|1].val += tree[u].add;
		tree[u].add = 0;//清空父节点的标记
	}
	static void build(int u,int l,int r) {
		if(l==r) {
			tree[u] = new Node(l,r,w[l],0);
		}else {
			tree[u] = new Node(l, r, 0, 0);
			int mid = l+r>>1;
			build(u<<1,l,mid);
			build(u<<1|1,mid+1,r);
			pushUp(u);
		}
	}

    //区间修改:将[l,r]的值都+val
	static void modify(int u,int l,int r,int val) {
	    //如果这个节点的区间被完全覆盖,就加上懒标记
		if(tree[u].l>=l && tree[u].r<=r) {
			tree[u].add += val;
			tree[u].val += val;
		}else {//否则,不被完全覆盖,先把账算清再修改区间,最后更新到父节点
			pushDown(u);
			int mid = tree[u].l+tree[u].r>>1;
			if(mid>=l) modify(u<<1,l,r,val);
			if(mid+1<=r) modify(u<<1|1,l,r,val);
			pushUp(u);
		}
	}
	
	//区间求和
	static long query(int u,int l,int r) {
		if(tree[u].l>=l && tree[u].r<=r)
			return tree[u].val;
		//先算清账,再求内部的值
		pushDown(u);
		long mx = INF;
		int mid = tree[u].l+tree[u].r>>1;
		if(l<=mid) mx = Math.min(mx,query(u<<1,l,r));
		if(mid+1<=r) mx = Math.min(mx,query(u<<1|1,l,r));
		return mx;
	}
	//初始化
	build(1,1,n);

	//修改[l,r]内的每个数+d
	modify(1, l, r,d);
	
	//查询[l,r]
	query(1, l, n)
				
lazy线段树维护:区间和
class SegTree_lz{
	static int N = 101000;
	static Node[] tree = new Node[4*N];
	
	//向上更新:用子节点的值更新父节点
	static void pushUp(int u) {
		tree[u].val = tree[u<<1].val + tree[u<<1|1].val;
	}
	//向下更新:用父节点的状态更新子节点,把懒标记给儿子节点
	//把父亲的账给儿子算清
	static void pushDown(int u) {
	    //传递懒标记
		tree[u<<1].add += tree[u].add;
		tree[u<<1|1].add += tree[u].add;
		//传递
		tree[u<<1].val += tree[u].add * (tree[u<<1].r-tree[u<<1].l+1);
		tree[u<<1|1].val += tree[u].add * (tree[u<<1|1].r-tree[u<<1|1].l+1);
		tree[u].add = 0;//清空父节点的标记
	}
	static void build(int u,int l,int r) {
		if(l==r) {
			tree[u] = new Node(l,r,0,0);
		}else {
			tree[u] = new Node(l,r,0,0);
			int mid = l+r>>1;
			build(u<<1,l,mid);
			build(u<<1|1,mid+1,r);
			pushUp(u);
		}
	}

    //区间修改:将[l,r]的值都+val
	static void modify(int u,int l,int r,int val) {
	    //如果这个节点的区间被完全覆盖,就加上懒标记
		if(tree[u].l>=l && tree[u].r<=r) {
			tree[u].add += val;
			tree[u].val += val*(tree[u].r-tree[u].l+1);
		}else {//否则,不被完全覆盖,先把账算清再修改区间,最后更新到父节点
			pushDown(u);
			int mid = tree[u].l+tree[u].r>>1;
			if(mid>=l) modify(u<<1,l,r,val);
			if(mid+1<=r) modify(u<<1|1,l,r,val);
			pushUp(u);
		}
	}

	//区间求和
	static long query(int u,int l,int r) {
		if(tree[u].l>=l && tree[u].r<=r)
			return tree[u].val;
		//先算清账,再求内部的值
		pushDown(u);
		long res = 0;
		int mid = tree[u].l+tree[u].r>>1;
		if(l<=mid) res += query(u<<1,l,r);
		if(mid+1<=r) res += query(u<<1|1,l,r);
		return res;
	}
	static class Node{
			int l,r;
			long val;
			long add;//懒标记
			public Node(int l,int r,long val,long add) {
					this.l = l;this.r = r;this.val = val;this.add = add;
			}
	}
}

KMP

判断模式串(p)是不是主串(s)的子串,返回主串中出现子串的下标

时间复杂度O(n)

static char[] p,s;//p是模式串,s是主串
static int[] ne = new int[N];//p的next数组,ne[i]=j表示相等的前后缀最大长度
//n是模式串的长度,m是主串的长度。

//1.计算模式串p的next数组(背过)
static void getNext() {
    for(int i=2,j=0;i<=n;i++) {
        while(j>0 && p[i]!=p[j+1]) 
            j=ne[j];
        if(p[i]==p[j+1])
            j++;
        ne[i] = j;
    }
}

//2.kmp匹配
for(int i=1,j=0;i<=m;i++) {
    while(j>0 && s[i]!=p[j+1])
        j=ne[j];
    if(s[i]==p[j+1]) j++;
    if(j==n) {//匹配成功
        out.print(i-n+1+" ");//下标从1开始
        j = ne[j];//计算后面是否还有子串p
    }
}

关键点:

  • 模式串p,主串s。字符串下标都是从1开始。n是p的长度,m是s的长度。
  • 指针i指向主串,指针j指向模式串,每一轮是将i与j+1的位置比较,如果不满足,j就回退到ne[j]
  • 如果i和j+1位置相同,那么就j++
  • 为什么是j+1和i的位置比较???因为一旦找到不符合的,我们就需要找前一位前面的子串的next数组

字符串哈希

应用场景:给定一个字符串,快速判断这个子串的两个子串是否相等,时间复杂度O(1)

正常用substring(),最坏会达到O(n)

  • 前缀和思想
  • 把一个字符串映射成一个数,hash[i]存放前缀字符串str[1~i]的hash值
  • 初始化:hash[i] = hash[i-1]*P + str[i]
  • 计算子串str[l~R]的哈希值 = hash[r] - hash[l-1]*pow(P,r-l+1).画图就能推出来
	static char[] str = new char[N];
	static int[] hash = new int[N],pow = new int[N];
	static int P = 131;//经验值
	
	//初始化
	pow[0]=1;
	hash[0]=0;
	for(int i=1;i<=n;i++) {
		hash[i] = hash[i-1]*P + str[i];
		pow[i] = pow[i-1]*P;//预处理次方
	}
	
	//计算子串的哈希值
	static int query(int l,int r) {
		return hash[r] - hash[l-1]*pow[r-l+1];
	}

数学

https://www.acwing.com/blog/content/681/

判断质数(试除法)

时间复杂度O(sqrt(n))

static int is_prime(int n){
        if(n<2) return 0;
        for (int i=2;i<=n/i;i++){
            if(n%i==0) return 0;
        }
        return 1;
}

质因数分解

时间复杂度O(sqrt(n))

  • 用treemap存储
  • 枚举2~根号n
  • 最多只有一个大于根号n的质因子k,并且这个大于根号n的质因子k的幂一定是1。(反证法:参考证明)
static Map<Integer,Integer> mp = new TreeMap<>();//TreeMap有序
static void divide(int n){
    for(int i=2;i<=n/i;i++){
        int cnt = 0;//java里不能mp[i]++,所以用变量cnt存储,再赋值
        while(n%i==0){
            n/=i;
            cnt++;
        }
        mp.put(i,cnt);
    }
    if(n>1) mp.put(n,1);
}
//输出
for(Integer key:mp.keySet()){
    int val = mp.get(key);
    if(val>0)
        System.out.println(key+" "+mp.get(key));
}

素数筛

求n以内的所有素数

1.埃氏筛法(适合求每一个素数)(推荐)

时间复杂度(O(nlogn))

  • 枚举n次
  • 将素数的所有倍数筛掉
static int[] st;
static int[] primes;
static int cnt;

void get_primes(int n){
    for(int i=2;i<=n;i++){
        if(st[i]==0){
            primes[cnt++]=i;
            for(int j=i+i;j<=n;j+=i)
                st[j]=1;
        }
    }
    //求素数个数
    cnt	
}
2.优化的埃氏筛法(不适合求每一个素数)

时间复杂度(O(nloglogn))

  • 枚举根号n次
  • 还是筛质数的倍数,但是是从i*i开始筛,因为比i * i小的数已经被前面的晒过了
void getPrimes(int n){
    for(int i=2;i<=n/i;i++){//根号n次
        if(st[i]==0){
            for(int j=i*i;j<=n;j+=i)//i*i开始
                st[j]=1;
        }
    }
    //求素数个数
    for(int i=2;i<=n;i++) 
        if(st[i]==0) ans++;
    //求每一个素数
    // for(int i=2;i<=n;i++)
    //     if(st[i]==0) primes[cnt++]=i;
}
3.线性筛(性能最好)
void get_primes(int n)
{
    for (int i = 2; i <= n; i ++ )
    {
        if (!st[i]) primes[cnt ++ ] = i;
        for (int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

求约数

时间复杂度 sqrt(n)

	static ArrayList<Integer> yue = new ArrayList<>();   
	static void solve(int n){
        for (int i = 1; i <= n/i; i++) {
            if(n%i==0){
                yue.add(i);
                if(n/i!=i)
                    yue.add(n/i);
            }
        }
        Collections.sort(yue);//约数从小到达排
    }

约数之和、约数个数

如果 N = p1^c1 * p2^c2 * ... *pk^ck
约数个数: (c1 + 1) * (c2 + 1) * ... * (ck + 1)
约数之和: (p1^0 + p1^1 + ... + p1^c1) * ... * (pk^0 + pk^1 + ... + pk^ck)

求组合数

求C(a,b)

用long存!!!

  • N 在3000以内(题意不用取模)
    static long[][] C = new long[N][N];
    static void init(){
        for (int i = 0; i < N; i++) {
            for (int j = 0; j <= i; j++) {
                if(j==0) C[i][j] = 1;
                else C[i][j] = C[i-1][j] + C[i-1][j-1];
                //对于要对答案取模的时候,在计算组合数的时候就要取模
                //else C[i][j] = (C[i-1][j] + C[i-1][j-1])%Mod;
            }
        }
    }
  • N在10^8以内、题目说要取模(用逆元求)

    C(a,b) = a!/(b! * (a-b)!) = a! * niyuan(b!) * niyuan((a-b)!)

    由于询问比较多,直接初始化阶乘和阶乘的逆元数组

    递推式:
    n! = (n-1)! * n
    1/(n!) = 1/(n-1)! * 1/n,其中1/n = qpow(n,Mod-2)(费马小定理)

    //jiechen[i] = i!
    static long[] jiechen = new long[N];
    //jiechenniyuan[i] = “i!的逆元”
    static long[] jiechenniyuan = new long[N];

	//初始化阶乘、阶乘的逆元
	static void init() {
		jiechen[0] = 1;
		jiechenniyuan[0] = 1;
		for(int i=1;i<N;i++) {
			jiechen[i] = jiechen[i-1] * i%Mod;
			jiechenniyuan[i] = jiechenniyuan[i-1] * qpow(i, Mod-2)%Mod;
		}
	}
	//C(a,b) = a!/(b! * (a-b)!) = a! * niyuan(b!) * niyuan((a-b)!)
	static long C(int a,int b) {
		long res = 1;
		res = res * jiechen[a]%Mod;
		res = res * jiechenniyuan[b]%Mod;
		res = res * jiechenniyuan[a-b]%Mod;
		return res;
	}
https://www.acwing.com/activity/content/code/content/5904493/

如果题目里面没有说要对组合数取模,那么这种方法不能使用!!!!!!!!

判断其他点是否在同一条直线上

已知两个点(x0,y0)(x1,y1)从而可以确定一条直线,再去判断一个点(x,y)是否在这条直线上面,有如下公式。用的是直线的一般形式。

由于除法会出问题,所以一般都是使用直线的一般形式: ( Y − y 0 ) / ( X − x 0 ) = ( y 1 − y 0 ) / ( x 1 − x 0 ) (Y-y0)/(X-x0) = (y1-y0)/(x1-x0) (Yy0)/(Xx0)=(y1y0)/(x1x0),化简得 ( Y − y 0 ) ∗ ( x 1 − x 0 ) = = ( X − x 0 ) = ( y 1 − y 0 ) (Y-y0) * (x1-x0) == (X-x0) = (y1-y0) (Yy0)(x1x0)==(Xx0)=(y1y0),我们只需要判断这个东西是不是相等即可判断是否在同一条直线上

  • 例子:x0,y0固定,统计经过(x0,y0)的所有直线中,至少需要多少根直线能够把所有给定的点覆盖掉。
		//这里x0,y0是固定的
		for(int i=0;i<n;i++) {//枚举第二个点,确定这条直线
			if(st[i]==1) continue;//这个点已经在其他的直线上了,就不需考虑这个点了
			int x1 = arr[i].x;
			int y1 = arr[i].y;
			ans++;//这是一条新的直线,计数+1
			for(int j=i+1;j<n;j++) {
				int x = arr[j].x;
				int y = arr[j].y;
                //使用直线的一般公式判断点(x,y)是否在这条直线上
				if((x-x0)*(y0-y1) == (y-y0)*(x0-x1)) 
					st[j] = 1;

			}
		}

费马小定理

描述:

如果一个数p是质数,并且a不是p的倍数,那么有a^(p-1) = 1 (mod p)。
除以p同余

等价于:(推荐)

如果一个数p是质数,并且a和p互质(等价于gcd(a,p)=1),那么有a^(p-1) = 1 (mod p)

底层逻辑:a是p的倍数,那么gcd(a,p) = p;如果a不是p的倍数,那么gcd(a,p)不一定是1,但是当a和p有一个是质数时,那么此时gcd(a,p)=1。

互质的理解:gcd(a,p)=1。如果两个数都是质数,那么一定互质。如果两个数有一个是质数,另外一个数不是它的倍数,那么这两个数互质,原因:质数只有1和他本身两个约数,不是倍数,那么他本身不能成为公约数了,那么公约数只能是1了。

官方解释:

在这里插入图片描述

一般配合求逆元使用

逆元

定义

若整数b,m互质,并且对于任意的整数 a,如果满足b|a,则存在一个整数x,使得 a/b≡a∗x(mod m) ,同余,则称x为b的模m乘法逆元,记为b^{-1}(mod m)。 

理解:

用逆元把除法变成了乘法。除法是一个很麻烦的事情,所以上述构造的含义:a除以一个数,我们要把他变成a乘以一个数,除以b,相当于乘以x,那么 b − 1 = x b^{-1} = x b1=x。即 b*x == 1(mod m)

求逆元(充要条件):

b存在乘法逆元的充要条件是 b与模数 m互质。

1.当模数 m为质数时,b^(m−2)即为b的模m的乘法逆元。(利用费马小定理)
2.当模数 m不是质数时,就需要用扩展欧几里得算法求出来的x就是一个逆元

证明看:费马小定理+逆元证明

欧拉函数

1∼N 中与 N 互质的数的个数被称为欧拉函数,记为 ϕ(N)。

若在算数基本定理中,下图中的p是n的质因子
在这里插入图片描述

int solve(int n){
    int res=n;
    for(int i=2;i<=n/i;i++){
        if(n%i==0){
            res =res /i * (i-1);
            while(n%i==0)n/=i;
        }
    }
    if(n>1) res = res/n*(n-1);
    return res;
}

扩展欧几里得算法

裴蜀定理
  • 若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,都有ax+by都一定是d的倍数。(充要的)
    特别地,一定存在整数x,y,使ax+by=d成立。

换句话说:

  • 两个整数a,b,方程a*x+b*y=m有解,当且仅当m是gcd(a,b)的倍数(充要)

扩展欧几里得代码:

// 求x, y,使得ax + by = gcd(a, b)
// 扩展欧几里得:求解方程ax+by=gcd(a,b)的解
// x=y′ y=x′−[a/b]*y′
static int x, y;//全局变量,替代引用
int exgcd(int a,int b){
    if(b==0){
        x=1,y=0;
        return a;
    }

    int gcd=exgcd(b,a%b);//递归调用
    int tmp = x;
    x=y,y=tmp-a/b*y;
    return gcd;
}

	//结果说明
	1.exgcd()的返回值是最大公约数
 	2.最后的(x,y)是方程ax + by = gcd(a,b)的解
    3.如果exgcd()的结果是1(那么a和b互质,就存在逆元),那么x是a的逆元(x可能是负数,所以答案是getMod(x))
        
    参考:https://www.cnblogs.com/kksk/p/13069795.html  

扩展欧几里得求逆元

板子已经给出来了,下面给出说明。

目标:求a的逆元x,a*x==1(% mod),a不是质数

我们假设逆元存在,已知逆元存在的充要条件是:gcd(a,mod) == 1

已知裴蜀定理:ax + by = gcd(a, b)的方程一定有解

另b = mod

则,a * x + mod * y = gcd(a,mod) = 1

两边模mod得: a*x % mod = 1,即 a * x = 1(%mod)

此时的x就是a的模mod的逆元,x也就是方程ax+by=gcd(a,b)的解的横坐标

在这里插入图片描述

在这里插入图片描述

1~n中k的倍数的个数

1~n中k的倍数的个数可以表示为:n/k
举例:1~8中2的倍数的个数为:8/2=4

n!中质因子p的个数

根据公式,n!中有质因子p的个数为:
( n p + n p 2 + n p 3 + . . . ) (\frac {n}{p}+\frac {n}{p^2}+\frac {n}{p^3}+...) pn+p2n+p3n+...)
时间复杂度为:O(logn)

int divide(int n,int p){
    int ans=0;
    /*方案1
    while(n){
        ans+=n/p;
        n/=p;//不能写成p*=p;
    }
    */
    //方案2
    for(int i=p;n/i;i*=p)
        ans+=n/i;
    return ans;
}

参考:https://blog.csdn.net/Supreme7/article/details/115796373

搜索

BFS

    static char[][] arr = new char[N][N];
    static int[][] vis = new int[N][N];
    static int[][] cen = new int[N][N];
    static int t,r,c;
    static int sx,sy,ex,ey;
    static int[] dx = {1,0,-1,0};
    static int[] dy = {0,1,0,-1};

   static void bfs() {
       for(int i=0;i<N;i++) {
           Arrays.fill(cen[i], INF);
           Arrays.fill(vis[i], 0);
       }

       Queue<PII> q = new LinkedList<>();
       //起点入队
       q.add(new PII(sx,sy));
       cen[sx][sy] = 0;
       vis[sx][sy] = 1;

       while(q.size()>0) {
           PII top = q.poll();
           int x = top.x, y = top.y;
           for(int i=0;i<4;i++) {
               int nx = x + dx[i],ny = y + dy[i];
               if(check(nx,ny)==1) {
                   q.add(new PII(nx,ny));
                   vis[nx][ny] = 1;
                   cen[nx][ny] = cen[x][y]+1;
                   ans++;//访问的不同节点个数
               }
           }
       }
       return cen[ex][ey];
   }
class PII{
	int x;
	int y;
	public PII(int x,int y) {
		this.x = x;
		this.y = y;
	}
}

    static int check(int x,int y) {
        if(x>=1 && x<=r && y>=1 && y<=c && vis[x][y]==0 && arr[x][y]=='.')
            return 1;
        return 0;
    }

    public static void main(String[] sss) {
            int ans = bfs();
            if(ans!=INF)
                System.out.println(ans);
            else 
                System.out.println("oop!");
    }
}

BFS遍历前k层所有节点的模板

就遍历k次,每一次初始时队列中元素的个数sz,让sz个

    static int bfs(int u) {
        Queue<Integer> q = new LinkedList<Integer>();
        Arrays.fill(st, 0);
        q.add(u);
        st[u] = 1;
        int ans = 0;//答案不算起点

        for(int level = 0;level < k;level++) {//BFS最多k层
            int sz = q.size();//获得当前这一层的所有节点个数
            while(sz-->0) {//把当前这层的所有节点出队
                Integer top = q.poll();
                for(int i=h[top];i!=-1;i=ne[i]) {
                    int j = e[i];
                    if(st[j]==1) continue;
                    q.add(j);
                    st[j]=1;
                    ans++;
                }
            }
        }
        return ans;
    }

枚举上下左右四个方向

static int[] dx = {1,0,-1,0};
static int[] dy = {0,1,0,-1};
for(int i=0;i<4;i++){
	int nx = x + dx[i];
	int ny = y + dy[i];
}

枚举上下左右的八个方向

for(int i=x-1;i<=x+1;i++){
	for(int j=y-1;j<=y+1;j++) {
		if(i==x && j==y) continue;//去除中间元素本身
		//具体代码
	}
}

FloodFill

枚举每个位置,如果是水域,并且没有被标记过,那么就搜索这一整片池塘,搜索的过程可以是bfs,也可以是dfs,对于搜索过的每一块水域,都标记一下。

https://www.acwing.com/activity/content/code/content/6361986/

图论

图的存储

邻接表

专栏下其他博客有

import java.util.*;
public class Main {
	static int N = 6060;
	static int[] h = new int[N],e = new int[N],ne = new int[N];//邻接表
	static int idx;
	static void add(int a,int b){
		e[idx] = b;
		ne[idx] = h[a];
		h[a] = idx++;
	}
}
//初始化头数组
Arrays.fill(h,-1);

//遍历root的所有子节点
for(int i = h[root];i!=-1;i = ne[i]){
	int j = e[i];
	//...
}
邻接表2

用来快速得到顶点的所有邻边条数

leetcode中比较常见

ArrayList<Integer>[] g = new ArrayList[N];

//初始化
for(int i=0;i<n;i++)
    g[i] = new ArrayList<Integer>();
    
//顶点a,b中间添加一条边
g[a].add(b);

Dijkstra

O (mlogn)

	static int[] h = new int[N],e = new int[M],ne = new int[M],w = new int[M];
	static int idx;
	static int[] dist = new int[N],st = new int[N];
	static void add(int a,int b,int c) {
		e[idx] = b;
		w[idx] = c;
		ne[idx] = h[a];
		h[a] = idx++;
	}
	static int dijkstra() {
		Arrays.fill(dist, INF);
		PriorityQueue<PII> q = new PriorityQueue<PII>((a,b)->(a.dist-b.dist));//小根堆
		q.add(new PII(0,1));
		dist[1]=0;

		while(q.size()>0) {
			PII top = q.poll();
			//如果这个点的最短路确定了,那么直接跳过
			if(st[top.ver]==1) continue;
			st[top.ver]=1;
			
			for(int i=h[top.ver];i!=-1;i=ne[i]) {
				int j = e[i];
				if(dist[j]>dist[top.ver]+w[i]) {
					dist[j] = dist[top.ver]+w[i];
					q.add(new PII(dist[j],j));
				}
			}
		}
		return dist[n];
	}
}
class PII{
	int dist,ver;
	
	public PII(int dist,int ver) {
		this.dist = dist;
		this.ver = ver;
	}
}

SPFA最短路

时间复杂度O(m*n)但是实际效率很高,用来替代朴素版的dijkstra

		//st数组用来存储当前节点是否在队列中
		static int[] st = new int[N],dist = new int[N];

        static int spfa() {
            Arrays.fill(dist, INF);
            Queue<Integer> q = new LinkedList<Integer>();
            
            q.add(1);
            st[1] = 1;
            dist[1] = 0;
            
            while(q.size()>0) {
                Integer top = q.poll();
                st[top] = 0;//不在队列了,置为0
                for(int i=h[top];i!=-1;i=ne[i]) {
                    int j = e[i];
                    if(dist[j] > dist[top] + w[i]) {
                        dist[j] = dist[top] + w[i];
                        if(st[j]==0) {//不在队列中,就入队
                            st[j]=1;
                            q.add(j);
                        }
                    }
                }
            }
            return dist[n];
        }

Floyd

  • floyd算法只需要一个二维数组变量g[][],运行前是一个邻接矩阵,运行后是一个记录最短路的dist数组,g [ i ] [ j ]表示i到j的最短路
  • 这个算法用邻接矩阵存储(朴素dijkstra用邻接矩阵,堆优化用邻接表,spfa用邻接表,floyd用邻接矩阵)
  • 初始化,对于所有的i!=j的位置,g数组初始化成INF。
    static int floyd(int[][] g) {
        for(int k=1;k<=n;k++) 
            for(int i=1;i<=n;i++) 
                for(int j=1;j<=n;j++) 
                    g[i][j] = Math.min(g[i][j], g[i][k]+g[k][j]);
        return g[1][n];
    }

    //初始化,在输入之前
    for(int i=0;i<n;i++) {
        Arrays.fill(g[i], INF);
        tie[i][i] = gon[i][i] = 0;  //自己到自己的距离为0      
    }

	//最短距离
	g[1][n];//表示从顶点1到顶点n的最短路径

Kruskal

在这里插入图片描述

    static int[] p = new int[N];
    static Edge[] edges = new Edge[M];

    static int kruskal() {
        Arrays.sort(edges,1,m+1);
        int cnt = 0,ans = 0;

        for(int i=1;i<=m;i++) {
            int x = edges[i].x;
            int y = edges[i].y;
            int w = edges[i].w;
            int a = find(x),b = find(y);
            if(a!=b) {
                p[a] = b;
                cnt++;
                ans += w;
            }
        }
        return cnt==n-1?ans:INF;
    }

	class Edge implements Comparable<Edge>{
        int x,y,w;
        public Edge(int x,int y,int w) {
            this.x = x;
            this.y = y;
            this.w = w;
        }
        @Override
        public int compareTo(Edge o) {
            return Integer.compare(w, o.w);
        }
    }

    static int find(int x) {
        if(x==p[x]) return x;
        return p[x] = find(p[x]);
    }

bellmanFord算法求最多k条边的最短路

应用场景:求边数最大是k的最短路

循环k次,每一次只用上一轮的dist数组更新,所以在循环边时把dist保存为tmp,防止本轮使用了这一轮的dist值

	static Edge[] edges = new Edge[N];
    static int[] dist = new int[N],tmp = new int[N];
	static void bellmanFord() {
            Arrays.fill(dist,INF);
            dist[1]=0;
            for(int i=0;i<k;i++) {//循环k次,代表
            	//复制当前的dist,表示上一轮的dist结果,这样才能找到边数<=k的
                tmp = dist.clone();
                for(int j=0;j<m;j++) {
                    Edge e = edges[j];
                    dist[e.b] = Math.min(dist[e.b], tmp[e.a]+e.c);
                }
            }
        }
    class Edge{
        int a,b,c;
        public Edge(int a,int b,int c) {
            this.a = a;
            this.b = b;
            this.c = c;
        }
    }

拓扑排序

只有有向无环图才有拓扑序列,图不一定要连通

1.所有入度为0的结点入队
2.当队列(栈)不为空时循环
    2.1取队头,出队,记录序列
    2.2遍历队头顶点指向的顶点,使该顶点入度-1
    2.3入度为0时入队

时间复杂度 O(V+E)
注意:

  • cnt用来存储已经输出的顶点的个数
  • 只有有向无环图才有拓扑序列
  • 如果一个图有环,最后终止循环时的cnt一定是小于n的
  • 所以只需判断cnt==n? 如果相等,就有拓扑序列,否则就不存在
	int[] d = new int[N];//存放入度
	int[] print = new int[N];//记录答案
	int cnt;

	static boolean topSort() {
		Queue<Integer> q = new LinkedList<Integer>();
		for(int i=1;i<=n;i++) {
			if(d[i]==0)
				q.add(i);
		}
		while(q.size()>0) {
			Integer top = q.poll();
			print[cnt++] = top;
			for(int i=h[top];i!=-1;i=ne[i]) {
				int j = e[i];
				d[j]--;
				if(d[j]==0) {
					q.add(j);
				}
			}
		}
		return n==cnt;
	}

染色法判定二分图

  • 二分图概念:

    • 二分图:图中点通过移动能分成左右两部分,左侧的点只和右侧的点相连,右侧的点只和左侧的点相连。
    • 一个图是二分图的充要条件是:当且仅当图中不含有奇数环
    • 奇数环:由奇数条边形成的一个环
  • 应用:判断图是否是二分图

  • 时间复杂度O(n)

  • 染色法思路:

  1. 开始对任意一未染色的顶点染色。

  2. 判断其相邻的顶点中,若未染色则将其染上和相邻顶点不同的颜色。

  3. 若已经染色且颜色和相邻顶点的颜色相同则说明不是二分图,若颜色不同则继续判断。

  4. bfs和dfs可以搞定!

		static int[] color = new int[N];

		//U是节点编号,c是节点u应该变成的颜色
        static boolean dfs(int u,int c) {
            color[u] = c;
            for(int i=h[u];i!=-1;i=ne[i]) {
                int j = e[i];
                if(color[j]==0) {
                    //return dfs(j,c==1?2:1);//这么写会WA
                    boolean res = dfs(j,c==1?2:1);
                    if(res==false) return false;//只有当返回false的时候,才能得到:不是二分图的结论,如果返回true不能返回值,而要继续搜索
                }else if(c==color[j])
                    return false;
            }
            return true;
        }

		int flag = 1;
        for(int i=1;i<=n;i++) {
            if(color[i]==0) {
                boolean res = dfs(i,1);
                if(res==false) {
                    flag = 0;
                    break;
                }
            }
        }
		if(flag==1) System.out.println("Yes");
        else System.out.println("No");

匈牙利算法

二分图最大匹配问题

https://www.acwing.com/activity/content/code/content/6469504/

  • 应用:匈牙利算法解决二分图的最大匹配问题

  • 思路:

    • 类比:一张桌左右两旁分别有n1个男生,n2个妹子。图中的边是暧昧的对象。然后看看最多能有多少对在一起。
    • 枚举左边的所有节点,依次判断当前左边节点的所有右边暧昧对象,如果右边的暧昧对象没有对象,那么就直接在一起;否则如果暧昧对象已经有对象了,那么就找到这个妹子的对象,他在他的暧昧对象中重新去找,看看他能不能找到一个其他的暧昧对象,如果能够找到一个,那就让他们在一起,从而这个妹子就是我的了。
  • 时间复杂度O(m*n),但是实际的执行效率高很多

  • 模板

		//match[i]=j表示右半边节点i当前匹配的是左半边的节点j
		//st是临时预定数组,st[i]表示当前轮次的右半边节点i是否被标记过
		static int[] match = new int[N],st = new int[N];
		
		//find函数:左边节点u向右边匹配,返回值为是否能够匹配成功
		//返回值:如果加入x来参与模拟配对,会不会使匹配数增多
		static boolean find(int u) {
			for(int i=h[u];i!=-1;i=ne[i]) {
				int j = e[i];
				if(st[j]==0) {//如果在这一轮模拟匹配中,这个女孩尚未被预定
					st[j] = 1;//那u就预定这个女孩了
					//如果女孩j没有男朋友,或者她原来的男朋友能够预定其它喜欢的女孩。配对成功,更新match
					if(match[j]==0 || find(match[j])) {
						match[j] = u;
						return true;
					}
				}
			}
			//自己中意的全部都被预定了。配对失败。
			return false;
		}

		//枚举所有男生,依次进行匹配
        for(int i=1;i<=n1;i++) {
            //因为每次模拟匹配的预定情况都是不一样的所以每轮模拟都要初始化
            Arrays.fill(st,0);//每一轮都进行匹配
            if(find(i)) ans++;
        }

LCA(最近公共祖先)

  • depth[i] = j表示节点i的层数(深度)是j

  • fa[u][i]表示u向上跳2i层的祖先节点,第二维开多大取决于节点个数最大值,比如节点有5e5,那么第二维就开到20,因为220 = 1e6,最大指数就是19

  • 节点编号是从1开始,0会作为超出边界的哨兵

  • 思路

    • 1.预处理st表(倍增)

      从小到大枚举,fa[u][i] = fa[fa[u][i-1]][i-1]

    • 2.lca查询(二进制拆分)从大到小枚举

      • 1.把a,b跳到同一层
      • 2.a,b同时向上跳
		static int[] depth = new int[N];//存储节点的层数
		static int[][] fa = new int[N][20];//最大2^19次方
		static int n,m,start;
		//st表预处理(计算倍增数组)O(nlogn)
		static void dfs(int u,int father) {
			depth[u] = depth[father]+1;
			fa[u][0] = father;
			for(int i=1;i<=19;i++) //递推
				fa[u][i] = fa[fa[u][i-1]][i-1];//先跳2^i-1,再跳2^i-1

			for(int i=h[u];i!=-1;i=ne[i]) {
				int j = e[i];
				if(j==father) continue;
				dfs(j,u);
			}
		}
		
		//lca求公共祖先O(logn)
		static int lca(int a,int b) {
			if(depth[a]<depth[b]) {
				int t = a;
				a = b;
				b = t;
			}
			//1.把a和b移到同一层
			for(int i=19;i>=0;i--) 
                //如果跳2^i层还是在b的下面,就跳到这个位置
				if(depth[fa[a][i]]>=depth[b])
					a = fa[a][i];
			if(a==b) return b;
			//2.a和b同时向上找到最近公共祖先的下一层节点
			for(int i=19;i>=0;i--) {
                //如果a和b跳上去不是同一个节点(这里的条件舍去了跳出边界的情况)
				if(fa[a][i] != fa[b][i]) {
					a = fa[a][i];
					b = fa[b][i];
				}	
			}
			return fa[a][0];//再向上走1层就到了lca节点了
		}
		
		//主函数
        dfs(start,0);//不能是-1,因为father要用到depth
        m = sc.nextInt();
        while(m-->0) {
            int x = sc.nextInt();
            int y = sc.nextInt();
            int ans = lca(x,y);
            if(ans==x) System.out.println(1);
            else if(ans==y) System.out.println(2);
            else System.out.println(0);
        }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 例题
    • AcWing 1171. 距离(顶点距离)
    • AcWing 4962. 景区导游(顶点距离)

树上差分

差分数组是可以用O(1)将区间内元素+c,最后只需O(n)遍历数组。树上差分用于高效解决树上的将区间内元素+c。

按点差分

定义树上差分数组p,p[i]+=c表示将树中根节点向节点i的路径上的所有边的值都+c,所以怎么将树中两个节点a,b路径上的所有节点都+c呢,操作就是:

p[a]+=c;
p[b]+=c;
p[lca]-=c;
p[fa[lca][0]]-=c

其中lca表示a,b的最近公共祖先,fa[lca][0]表示的是节点lca的父节点

QQ截图20230601125242.png

拓展:树上顶点间的距离

距离公式:
	顶点a到顶点b的距离 =  dist[a] + dist[b] - 2*dist[lca]
	
	新增一个数组dist,表示顶点到起点的距离
	
	dist更新的方式如下:
	static void dfs(int u,int father) {
        dep[u] = dep[father]+1;
        fa[u][0] = father;
        for(int i=1;i<19;i++)
            fa[u][i] = fa[fa[u][i-1]][i-1];
        for(int i=h[u];i!=-1;i=ne[i]) {
            int j = e[i];
            if(j==father) continue;
            dist[j] = dist[u] + w[i];
            dfs(j,u);
        }
    }
按边差分

如果要维护边的差分数组,比如对树上a,b路径上的所有边都+c,操作就是:

p[a]+=c;
p[b]+=c;
p[lca(a,b)]-=2*c

这里是将边下放到节点,比如p[a] = b,表示顶点a为下边界时的那条边

最后从根节点向下遍历整张图中所有节点,先递归子节点,再将节点从下向上更新前缀和数组(差分的求和),板子如下

    //先递后归
	static void dfs(int u,int father) {
		for(int i=h[u];i!=-1;i=ne[i]) {
			int j = e[i];
			if(j==father) continue;
			dfs(j,u);
			chafen[u] += chafen[j];
			//如果要在这判断边,是使用chafen[j]这条边,因为这条边的值已经确定,而chafen[u]还没确定
		}
	}

需要注意的是,树上差分算法涉及到LCA,如果是用倍增求LCA,那么每一个求lca是O(logn)的复杂度;如果是用Tarjan求LCA,那么预处理后每一个求lca是O(1)的

  • 例题
    • AcWing 352. 闇の連鎖(边的树上差分)
    • luogu P3258 松鼠的新家(顶点的树上差分)

排序算法

快速排序

时间复杂度O(nlogn)

递归树的高度是logn,每一层的元素和还是n,所以nlogn

        static void quickSort(int[] arr,int l,int r) {
            if(l>=r) return ;
            int x = arr[l],i = l-1,j = r+1;
            while(i<j) {
                while(arr[++i] < x);
                while(arr[--j] > x);
                if(i<j) {
                    int t = arr[i];
                    arr[i] = arr[j];
                    arr[j] = t;
                }
            }
            quickSort(arr, l, j);
            quickSort(arr, j+1, r);
        }

快排可以求第k大的数

归并排序

		static int[] tmp = new int[N];//临时数组
        static void mergeSort(int[] a,int l,int r) {
            if(l>=r) return;
            int mid = l + r>>1;
            mergeSort(a, l, mid);
            mergeSort(a, mid+1, r);
            int k=0;//临时数组tmp的下标计数
            int i=l,j=mid+1;
            while(i<=mid && j<=r) {
                if(a[i]<=a[j]) tmp[k++] = a[i++];
                else tmp[k++] = a[j++];
            }
            while(i<=mid) tmp[k++] = a[i++];
            while(j<=mid) tmp[k++] = a[j++];

            for(i=l,j=0;i<=r && j<k;i++,j++) {
                a[i] = tmp[j];
            }
        }

归并排序可以用来求逆序对的数量

逆序对的数量

分治的思想,用O(nlogn)的时间复杂度,求出一个数列中逆序对的个数,修改归并排序的代码

        //返回区间内元素的逆序对的个数
        static long getNixudui(int[] q,int l,int r) {
            if(l>=r) return 0;
            int mid = l+r>>1;
            long res = getNixudui(q, l, mid) + getNixudui(q, mid+1, r);

            int i = l, j = mid+1;
            int k = 0;
            while(i<=mid && j<=r) {
                if(q[i]<=q[j]) tmp[k++] = q[i++];
                else {
                    res += mid - i +1;//当前j的逆序对的个数
                    tmp[k++] = q[j++];
                }
            }
            while(i<=mid) tmp[k++] = q[i++];
            while(j<=r) tmp[k++] = q[j++];
            for(i = l,j = 0;i<=r;i++,j++)
                q[i] = tmp[j];
            return res;
        }

链接:https://www.acwing.com/activity/content/code/content/5530302/

也可以用树状数组

        Node[] arr = new Node[N];
        int[] lisanhua = new int[N];
		//离散化
        for(int i=1;i<=n;i++){
            arr[i] = new Node(nums[i],i);
        }
        Arrays.sort(arr,1,n+1,(a,b)->a.val-b.val);
        for(int i=1;i<=n;i++)
            lisanhua[arr[i].id] = i;
		//记录个数
        int cnt = 0;
        for(int i=n;i>0;i--){//从后向前枚举
            cnt += query(lisanhua[i]-1);//找到小于lisanhua[i]的个数
            // System.out.println(query(lisanhua[i]-1));
            add(lisanhua[i],1);
        }
        return cnt;

堆是二叉树的结构,我们要利用堆实现以下五个操作:

  1. 插入一个数
  2. 求集合最值
  3. 删除最值
  4. 删除任意元素
  5. 修改任意元素

前三个操作可以采用STL直接实现,然而后两个就不行了。

堆删除最后一层之后是一个完全二叉树,我们以小根堆为例。

双堆找中位数
  • 题意:P1168 中位数。给定一个长度为 N 的非负整数序列 A,对于前奇数项求中位数。

  • 思路首先记录一个变量mid,记录答案(中位数)。建立两个堆,一个大根堆一个小根堆,大根堆≤mid的数,小根堆存 >mid的的数。所以我们向堆中加入元素时,就通过与mid的比较,选择加入哪一个堆。但我们在输出答案前需要对mid进行调整,如果小根堆和大根堆内元素相同,就无需处理,此时mid仍然是当前的中位数。如果两个堆中元素个数不同,那我们就需要进行调整。具体是把元素个数较多的堆的堆顶作为mid,上一次的mid加入元素较少的堆。

参考:https://www.luogu.com.cn/blog/zy-E/p1168-zhong-wei-shuo

博弈论

  • 基本概念

    • 先手必胜状态:先手进行某一个操作,留给后手是一个必败状态时,对于先手来说是一个必胜状态。即先手可以走到某一个必败状态。
    • 先手必败状态:先手无论如何操作,留给后手都是一个必胜状态时,对于先手来说是一个必败状态。即先手走不到任何一个必败状态。
  • 细节:

    • 下面的例子的最终态都是必败态,并且这个最终态都是无求可拿,没有任何选择时就输

      • 如果告诉了必败态不是没有选择的情况时,那就控制初始值

        例题:lqb真题:取球游戏

      • 如果题目告诉了必胜态的情况,要转换成必败态去做。

        例题:剪纸游戏

Nim游戏:石子

  • 题意:给定 n 堆石子,两位玩家轮流操作,每次操作可以从任意一堆石子中拿走任意数量的石子(可以拿完,但不能不拿),最后无法进行操作的人视为失败。

  • 结论:

    QQ截图20230519155452.png

  • 证明

QQ图片20230520143018.png

  • 拓展

    本题还可以求出如果先手必胜,那么输出先手的第一次选择。代码如下

    QQ截图20230520143508.png

Nim游戏:台阶

其实阶梯博弈经过转换可以变为Nim…把所有奇数阶梯看成N堆石子…做nim…把石子从奇数堆移动到偶数堆可以理解为拿走石子…就相当于几个奇数堆的石子在做Nim

  • 结论:奇数项异或和不等于0就是先手必胜

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 拓展

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SG函数概念

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • SG函数理论:多个独立局面的SG值,等于这些局面SG值的异或和。

SG函数模板

计算每堆石子的所有变化情况:sg函数,再异或

		static int[] sg = new int[N];//sg函数值
		
		//sg函数初始化
		Arrays.fill(sg,-1);//初始化成负数
		
		//计算节点x的sg函数(记忆化搜索)
		static int calSg(int x) {
			if(sg[x]!=-1) return sg[x];
			HashSet<Integer> se = new HashSet<Integer>();
			
			//具体的逻辑,会变
			for(int i=1;i<=k;i++) {
				if(x>=set[i])
					se.add(calSg(x-set[i]));
			}
			//计算mex(不在se中的最小的非0元素)
			for(int i=0;;i++) {
				if(!se.contains(i)) {
					return sg[x] = i;
				}
			}
		}
		
		//答案:
		for(int i=1;i<=n;i++) {
			int x = sc.nextInt();
			res ^= calSg(x);
		}
		if(res!=0) System.out.println("Yes");
		else System.out.println("No");

sg函数的例子,如下:集合。

SG函数:有向图游戏

计算k枚棋子的sg值,再异或,记忆化搜索

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SG函数:集合

计算sg函数时,枚举整个集合,不需要真的建图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Nim是Sg定理的特例

  • 普通的Nim定理其实就是Sg定理的特例,在普通的Nim中,sg(x)=x,所以不同算每个节点x的sg值了,因为就是x,所以就相当于所有值的异或。

    在Nim中,有n堆石子,分别画出n棵树,根节点分别是石子的值,下面的子节点都是一次操作,计算n个根节点的sg值,最后异或起来,如果非0就是先手必胜

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

SG函数:二维子节点的sg值

  • 题意:有N堆石子,选择一堆石子(个数是x),把他拿走,然后再添加两堆石子,这两堆石子,每堆石子的最大值是x-1,最小值是0。

  • 思路:这是sg函数的题,关键点在于:一堆石子x可以转移很多个子节点状态,每个子节点又是二维的,因为有两堆,表示为(i,j),需要计算这个二维子节点的sg值,又相当于一个局面拆分成了两个局面,由SG函数理论,多个独立局面的SG值,等于这些局面SG值的异或和。即sg(i,j) = sg(i)^sg(j)

  • 题解:https://www.acwing.com/activity/content/code/content/6473987/

SG函数:剪纸游戏

本题是更复杂的二维情况,根节点节点是二维的,每个子节点是两个二维节点

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

博弈论例题:

  • lqb真题:分石头(sg函数)

    https://www.lanqiao.cn/problems/2241/learning/?page=1&first_category_id=1&sort=students_count&tags=%E5%8D%9A%E5%BC%88%E8%AE%BA

  • lqb真题:取数游戏(sg函数+集合)

贪心

  • 区间的贪心问题,一般都是上来先按左端点或者右端点排序,方法:考虑区间a被区间b包含的情况,比如区间选点问题,此时就选择小的区间a,再画图对比一下,就知道选择按照右端点;比如区间覆盖问题,此时选择区间大的区间b,就是左端点排序。

区间选点

  • 题意:给定 N个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。输出选择的点的最小数量。

  • 贪心策略:根据右端点升序

  • 算法思路:r是等到ans++修改后才更新

    			Arrays.sort(arr,1,n+1,(a,b)->(a.b-b.b));
                int ans = 1;
                int r = arr[1].b;
                for(int i=2;i<=n;i++) {
                    if(arr[i].a > r) {
                        ans ++;
                        r = arr[i].b;
                    }
                }
    

最大不相交区间数量

最大不相交区间数==最少覆盖区间点数

  • 题意:给定 N个闭区间 [ai,bi],请你在选择若干区间,使得选中的区间之间互不相交。输出可选取区间的最大数量。

  • 贪心策略:根据右端点升序

  • 算法思路:r是等到ans++修改后才更新

    本题代码和区间选点代码完全一样
    

区间覆盖

  • 题意:给定 N个闭区间 [ai,bi],以及一个线段区间[s,t]。请你在选择尽量少的区间,将指定线段区间完全覆盖。

  • 贪心策略:根据左端点升序

  • 算法思路:双指针找到区间左端点在s前面,右端点的最大值,然后就选择该区间,下一轮的s就变成了当前区间右端点

    			for(int i=1;i<=n;i++) {
                    int j = i,r = -0x3f3f3f3f;//r是右端点最大值
                    //找到右端点的最大值
                    while(j<=n && arr[j].l<=start) {
                        r = Math.max(r,arr[j].r);
                        j++;
                    }
                    //如果最大值还是到不了下一次覆盖的起点,就直接返回
                    if(r<start) {
                        break;
                    }
                    ans++;
                    start = r;
                    if(r>=end) {
                        flag = 1;
                        break;
                    }
                    i = j-1;//下一轮指定
                }
    
    作者:weiambt
    链接:https://www.acwing.com/activity/content/code/content/6481372/
    来源:AcWing
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

区间分组

  • 题意:给定 N个闭区间 [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小

  • 贪心策略:根据左端点升序

  • 算法思路:堆维护所有组的右端点最小值。

  • 解释:使用小根堆维护所有组的右端点的最小值
    在已经存在的组中,没有右端点比当前区间的左端点小的,那么就新开一个组
    // 有右端点比当前区间的左端点小的,那么就加到这个组中
    //用堆的原因:只需要判断所有组中是否存在一个组的右端点比它小,所以直接维护右端点的最小值即可

    		PriorityQueue<Integer> q = new PriorityQueue<Integer>((a,b)->(a-b));
            for(int i=1;i<=n;i++) {
            	PII now = arr[i];
                //当前所有组的右端点最小值比左端点大,那么就新建组
                if(q.size()==0 || q.peek()>=now.l) {
                	q.add(now.r);
                	ans++;
                }else {//否则就加入到右端点最小的组中(这里其实加到哪一组都是一样的,为了方便加入到最小的组中)
                	q.poll();
                	q.add(now.r);
                }
    

绝对值不等式

  • 题意:在一条数轴上有N个点,A1~AN。寻找一个点,使得n个点到该点的距离和最小

  • 不等式:|x-a|+|x-b|>=|a-b|(当且仅当x在线段ab上时取等)

    推广:

      |x-a1|+|x-a2|+...|x-an|
    
    =(|x-a1|+|x-an|)+(|x-a2|+|x-an-1|)+...  
    
    >=|a1-an|+|a2-an-1|+...(每一个等号成立的条件都是x在范围之间)
    

    所以得出结论:

    当n为奇数时,x为n的中位数,即(n+1)/2

    当n为偶数时,x取中间两个数之间任意数都可,[n/2,n/2+1] ,方便写代码,这里也可以取(n+1)/2

  • 思路:升序排列,一次计算和中间值的差值

耍杂技的牛

推公式:https://www.acwing.com/activity/content/code/content/6500568/

  • 贪心推公式的思路:要想到贪心,画表格,相邻两个数,交换前、交换后的值,设定大小关系,使得交换前的最优

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/445643.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

寒假作业Day 10

寒假作业Day 10 一、选择题 1、下列数据结构中&#xff0c;不属于线性表的是( ) A.队列 B.顺序表 C.二叉树 D.链表 A. 队列&#xff1a;队列是一种特殊的线性表&#xff0c;它只允许在表的前端&#xff08;front&#xff09;进行删除操作&#xff0c;而在表的后端&#xff08…

【经管数据-更新】华证ESG评级得分数据(2009-2023年)

一、数据说明 参考《经济研究》中方先明&#xff08;2023&#xff09;的做法&#xff0c;将华证ESG评级进行赋值&#xff0c;指标包含C、CC、CCC、B、BB、BBB、A、AA、AAA共9个等级&#xff0c;将上市公司ESG 等级从低到高分别赋值为1至9 二、数据来源&#xff1a;世界银行&am…

Springboot进行web开发

创建springboot工程&#xff0c;基于2022版idea pom.xml文件中的插件爆红&#xff1a; 解决方法&#xff1a;给插件加<version>版本号</version> 版本号和<parent></parent>中的版本号一样。 另外有人说重启也可以解决爆红&#xff0c;可以试一下&a…

Stable diffusion(一)

Stable diffusion 原理解读 名词解释 正向扩散&#xff08;Fixed Forward Diffusion Process&#xff09;&#xff1a;反向扩散&#xff08;Generative Reverse Denoising Process&#xff09; VAE&#xff08;Variational AutoEncoder&#xff09;&#xff1a;一个用于压缩图…

【动态规划】【前缀和】【和式变换】100216. K 个不相交子数组的最大能量值

本文涉及知识点 动态规划汇总 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode 100216. K 个不相交子数组的最大能量值 给你一个长度为 n 下标从 0 开始的整数数组 nums 和一个 正奇数 整数 k 。 x 个子数组的能量值定义为 stren…

Swagger修改Api文档中的数据类型

swagger不陌生,API接口利器,本次要解决的问题是:我们知道前端在接收Long类型的属性时会出现精度问题,一般我们会在序列化的时候将Long类型的数字转换成String但是swagger的API文档中的类型还是Long,我们要解决的就是这个问题 不知道swagger怎么配置得可以看之前的文章:springb…

空间复杂度的OJ练习——轮转数组

旋转数组OJ链接&#xff1a;https://leetcode-cn.com/problems/rotate-array/ 题目&#xff1a; 思路&#xff1a; 通过题目我们可以知道这是一个无序数组&#xff0c;只需要将数组中的数按给定条件重新排列&#xff0c;因此我们可以想到以下几种方法&#xff1a; 1.暴力求解法…

【Tauri】(5):本地运行candle和 qwen 大模型,并测试速度

1&#xff0c;本地运行candle 关于candle项目 https://github.com/huggingface/candle Hugging Face 使用rust开发的高性能推理框架。 语法简单&#xff0c; 风格与 PyTorch 相似。 CPU 和 Cuda Backend&#xff1a;m1、f16、bf16。 支持 Serverless&#xff08;CPU&#xff…

React 从0到1构建企业级框架基于Antd Designer

一、 create-react-app 创建 cms-front 二、 删除不必须要的文件形成如下结构 1. React版本为17版本 public 文件夹下保留 favicon.ico 偏爱图标index.html资源文件 2.src 保留 index.js 入口文件和app.js(基于spa原则)单文件即可 三、配置eslint 1. 安装 eslint. npm inst…

章六、集合(1)—— Set 接口及实现类、集合迭代、Map 接口、Collections类

一、 Set 接口及实现类 Set接口不能存储重复元素 Set接口继承了Collection接口。Set中所存储的元素是不重复的,但是是无序的, Set中的元素是没有索引的 Set接口有两个实现类&#xff1a; ● HashSet &#xff1a;HashSet类中的元素不能重复 ● TreeSet &#xff1a;可以给Set集…

低密度奇偶校验码LDPC(十)——LDPC码的密度进化

一、密度进化的概念 二、规则LDPC码的密度进化算法(SPA算法) 算法变量表 VN更新的密度进化 CN更新的密度进化 算法总结 程序仿真 参考文献 [1] 白宝明 孙韶辉 王加庆. 5G 移动通信中的信道编码[M]. 北京: 电子工业出版社, 2018. [2] William E. Ryan, Shu Lin. Channel Co…

Spring-AOP基础(全在这里)

八股文部分来源于网络&#xff0c;例子为原创 OOP(Object-oriented programming) 也就是面向对象编程&#xff0c;继承&#xff0c;封装&#xff0c;多态。 局限性 静态语言&#xff1a;类结构一旦定义&#xff0c;不容易被修改(并不是无法修改)。只能侵入性扩展&#xff1a…

太强了!最全的大模型检索增强生成(RAG)技术概览!

本文是对检索增强生成&#xff08;Retrieval Augmented Generation, RAG&#xff09;技术和算法的全面研究&#xff0c;对各种方法进行了系统性的梳理。文章中还包含了我知识库中提到的各种实现和研究的链接集合。 鉴于本文的目标是对现有的RAG算法和技术进行概览和解释&#…

【深度学习笔记】6_5 RNN的pytorch实现

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 6.5 循环神经网络的简洁实现 本节将使用PyTorch来更简洁地实现基于循环神经网络的语言模型。首先&#xff0c;我们读取周杰伦专辑歌词…

【C++】list模拟实现list迭代器失效问题

list模拟实现&list迭代器失效问题 一&#xff0c;list模拟实现1. list的主要框架接口模拟2. list构造&拷贝构造&析构3. list迭代器3.1 普通迭代器3.2 const迭代器 4. 增删查改 二&#xff0c;迭代器失效问题1. list的迭代器失效原因2. 解决办法 一&#xff0c;list…

个推与华为深度合作,成为首批支持兼容HarmonyOS NEXT的服务商

自华为官方宣布HarmonyOS NEXT鸿蒙星河版开放申请以来&#xff0c;越来越多的头部APP宣布启动鸿蒙原生开发&#xff0c;鸿蒙生态也随之进入全新发展的第二阶段。 作为华为鸿蒙生态的重要合作伙伴&#xff0c;个推一直积极参与鸿蒙生态建设。为帮助用户在HarmonyOS NEXT上持续享…

PostGIS 中的 K-Means 聚类操作及应用

K-Means算法&#xff1a; K-means 是数据科学和商业的基本算法。让我们深入了解一下。 1. K-means是一种流行的用于聚类的无监督机器学习算法。它是用于客户细分、库存分类、市场细分甚至异常检测的核心算法。 2. 无监督&#xff1a;K-means 是一种无监督算法&#xff0c;用于…

《MySQL数据库》day2--连接查询、子查询、union、limit、DML语句

文章目录 1.把查询结果去除重复记录 -》distinct2.连接查询2.1什么是连接查询&#xff1f;2.2连接查询的分类2.3笛卡尔积现象2.4内连接2.4.1内连接之等值连接。2.4.2内连接之非等值连接2.4.3内连接之自连接 2.5外连接2.6三张表&#xff0c;四张表怎么连接&#xff1f; 3.子查询…

从0到1入门C++编程——11 函数对象及算法介绍

文章目录 函数对象1、谓词2、内建函数对象(1) 算术仿函数(2) 关系仿函数(3) 逻辑仿函数 常用算法1、常用遍历算法(1) for_each(2) transform 2、常用查找算法(1) find和find_if(2) find_if(3) adjacent_find(4) binary_search(5) count(6) count_if 3、常用排序算法(1) sort(2)…

奇舞周刊第521期:实现vue3响应式系统核心-MVP 模型

奇舞推荐 ■ ■ ■ 实现vue3响应式系统核心-MVP 模型 手把手带你实现一个 vue3 响应式系统&#xff0c;代码并没有按照源码的方式去进行组织&#xff0c;目的是学习、实现 vue3 响应式系统的核心&#xff0c;用最少的代码去实现最核心的能力&#xff0c;减少我们的学习负担&…