• 7.16复习笔记


    今天就不算是复习笔记了吧,因为也没有具体的讲解,不过对于这些我确实没什么想说的

    因为下午调题调的比较嗨,所以就把一些比较简单的都放到了今天统一写了一下

    一、AC自动机

    一些经典的问题:
    1. 求各个模式串在文本串中出现了几次:

      方法: 在AC自动机上先跑一边文本串,记录一下每个点被经过的次数,那么单词被经过的次数就是他的结尾在fail树中的子树的权值和

    2. 找是否有一个匹配不上匹配串的环:

      方法: 把所有能匹配上匹配串的节点匹配记录下来,dfs从跟开始走所有我可以走的点,如果我走到了一个之前走过的点就说明出环了

      实现: 一边调用fail一边更新当前点是否能走,因为fail记录的是前缀等于后缀,后缀深度一定比前缀大,所以fail一定在之前就走过了

    3. (n)个串总共出现(m)次的最短长度

      方法: 求两两单词结尾之间的最短距离,初始矩阵(a_{i,j})表示结尾(i)和结尾(j)之间不经过其他结尾的最短距离,矩阵快速幂(m-1)

      更新成(a_{i,j})表示结尾(i)到结尾(j)之间经过其他(m-2)个结尾的最小距离,现在总共经过了(m)个单词结尾,再加上第一个单词的长度,就是最短长度,一共(nm)种情况取最小的一种

    4. 每次删掉文本串中的最早出现的模式串,剩下的字符继续拼在一起

      方法: 跑AC自动机,模拟栈,发现匹配到一个单词,把属于这个单词的字符全部弹出,继续从之前匹配的位置继续往下找

    int tot = 1;
    void getTrie(char s){
    	int root = 1,len = strlen(s+1);
    	for (int i = 1;i <= len;i++){
    		int nxt = s[i]-'a';
    		if (!ch[root][i]) ch[root][i] = ++tot;
    		root = ch[root][i];
    	}
    }
    void getFail(){
    	queue<int> q;q.push(1);
    	for (int i = 0;i < 26;i+++) ch[0][i] = 1;
    	while (!q.empty()){
    		int root = q.front();q.pop();
    		for (int i = 0;i < 26;i++){
    			if (!ch[root][i]) ch[root][i] = ch[fail[root]][i];
    			else{
    				fail[ch[root][i]] = ch[fail[root]][i];
    				q.push(ch[root][i]);
    			}
    		}
    	}
    }
    

    二、tarjan 2-SAT

    注意我连边的时候一定是xxx时一定yyy的时候,让xxx向yyy连边

    tajan染色后方案为编号小的那一个

    void tarjan(int x){
    	dfn[x] = low[x] = ++cnt;
    	st[++top] = x;
    	for (int i = head[x];i;i = ed[i].nxt){
    		int to = ed[i].to;
    		if (!dfn[to]){
    			tarjan(to);
    			low[x] = min(low[x],low[to]);
    		}
    		else if (!col[to]) low[x] = min(low[x],dfn[to]);
    	}
    	if (dfn[x] == low[x]){
    		int y;++color;
    		while (y = st[top--]){
    			col[y] = color;
    			if (x == y) break;
    		}
    	}
    }
    

    割点 :在一个无向图中,如果删除某个顶点,这个图就不再连通(任意两点之间无法相互到达),那么这个顶点就是这个图的割点。

    割边(桥):在一个无向图中删除某条边后,图不再连通,那么这条边就是这个图的割边(也叫作桥)

    求法

    • 割点: 一个顶点(x)是割点,当且仅当满足:
    1. (x)为树根,且(x)有多于一个子树。

    2. (x)不为树根,且满足(x)(to)在搜索树中的父亲,并使得(low_{to}le dfn_x).(因为删去(x)(to)以及(to)的子树不能到达(x)的其他子树以及祖先)

    code:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a; 
    }
    const int maxn = 1e5+10;
    int n,m;
    struct node{
    	int to,next;
    }ed[maxn*2];
    int head[maxn*2],tot;
    void add(int u,int to){
    	ed[++tot].to = to;
    	ed[tot].next = head[u];
    	head[u] = tot;
    }
    int dfn[maxn],low[maxn],flag[maxn],cnt,child;
    void tarjan(int x,int fa){
    	dfn[x] = low[x] = ++cnt;
    	for (int i = head[x];i;i = ed[i].next){
    		int to = ed[i].to;
    		if (!dfn[to]){
    			tarjan(to,fa);
    			low[x] = min(low[x],low[to]);
    			if (low[to] >= dfn[x]&&x != fa) flag[x] = 1;	
    			if (x == fa) child++;
    		}
    		low[x] = min(low[x],dfn[to]);
    	}
    	if (child >= 2&&x == fa) flag[x] = 1;
    }
    int main(){
    	n = read(),m = read();
    	for (int i = 1;i <= m;i++){
    		int x = read(),y = read();
    		add(x,y),add(y,x);
    	}
    	for (int i = 1;i <= n;i++){
    		if (!dfn[i]) child = 0,tarjan(i,i);
    	}
    	int tot = 0;
    	for (int i = 1;i <= n;i++){
    		if (flag[i]) tot++;
    	}
    	printf("%d
    ",tot);
    	for (int i = 1;i <= n;i++){
    		if (flag[i]) printf("%d ",i);
    	}
    	return 0;
    } 
    
    • 割边:
    1. 一条无向边((x,to))是桥,满足(low_{to}>dfn_x).(因为(to)想要到达(x)的父亲必须经过((x,to))这条边,所以删去这条边图不连通)

    2. 实现时因为是无向图,建反边两条边都要标记上,边从(1)开始编号,正向边(x)的反向边就是(x)^(1)

    code:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    using namespace std;
    int read(){
    	int x = 1,a = 0;char ch = getchar();
    	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
    	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
    	return x*a;
    }
    const int maxn = 3e6+10;
    int n,m;
    struct node{
    	int to,next;
    }ed[maxn*2];
    int head[maxn*2],tot = 1;
    void add(int u,int to){
    	ed[++tot].to = to;
    	ed[tot].next = head[u];
    	head[u] = tot;
    }
    int dfn[maxn],low[maxn],cnt,res;
    int bridge[maxn];
    void tarjan(int x, int fa) {
        dfn[x] = low[x] = ++cnt;
        for (int i = head[x];i;i = ed[i].next) {
            int to = ed[i].to;
            if (!dfn[to]) {
                tarjan(to, i);
                low[x] = min(low[x],low[to]);
                if (low[to] > dfn[x])
                    bridge[i] = bridge[i^1] = true;
            }
            else if (i != (fa^1)) low[x] = min(low[x],dfn[to]);
        }
    }
    int main(){
    	n = read(),m = read();
    	for (int i = 1;i <= m;i++){
    		int u = read(),v = read();
    		add(u,v),add(v,u);
    	}
    	for (int i = 1;i <= n;i++){
    		if (!dfn[i]) tarjan(i,0);
    	}
    	int res = 0;
        for (int i = 2; i < tot; i+=2)
            if (bridge[i]) res++;
    	printf("%d
    ",res);
    	return 0;
    }
    

    三、manacher

    因为今天没有把NOI2020做完,所以把之后几天的复习计划提前了,毕竟计划做完了才能心安理得

    解决回文串问题,其实不难发现在解决字符串问题的时候,我们通常都想要充分的利用之前处理过的信息

    void manacher(){
    	for (int i = 1,r = 0,mid = 0;i <= cnt;i++){
    		if (i <= r) f[i] = min(f[(mid << 1)-i],r-i+1);
    		while (a[i-f[i]] == a[i+f[i]]) f[i]++;
    		if (f[i]+i > r) r = f[i]+i-1,mid = i;
    	}
    }
    

    四、Nim博弈SG函数

    我觉得我之前写的博客就已经很详细啦,因为不涉及什么算法,就是打表找规律,这里就不总结了

    五、欧拉函数

    求不超过n且与n互质的正整数的个数

    void Euler(){
    	for (int i = 2;i < n;i++){
    		if (!E[i]){
    			for (int j = i;j < n;j += i){
    				if (!E[j]) E[j] = j;
    				E[j] = E[j]/i*(i-1); 
    			}
    		}
    	}
    }
    

    六、线性基

    往线性基里插入一个数

    void add(int x){
    	for (int i = 30;i >= 0;i--){
    		if (x&(1 << i)){
    			if (d[i]) x ^= d[i];
    			else{
    				d[i] =  x;
    				break;
    			}
    		}
    	}
    }
    

    查询最大异或和

    int getmax(){
    	int res = 0;
    	for (int i = 30;i >= 0;i--){
    		if (res^d[i] > d[i]) res ^= d[i];
    	}
    	return res;
    }
    

    查询k大异或和

    void init(){
    	for (int i = 1;i <= 30;i++){
    		for (int j = 1;j <= i;j++){
    			if (d[i]&(1 << (j-1))) d[i] ^= d[j-1];
    		}
    	}
    }
    int kth(int k){
    	if (k == 1&&tot < n) return 0;
    	if (tot < n) k--;
    	if (pow(2,tot) <= k) retun -1;
    	init();
    	int ans = 0;
    	for (int i = 0;i <= 30;i++){
    		if (d[i]){
    			if (k&1) ans ^= d[i];
    			k >>= 1;
    		}
    	}
    	return ans;
    }
    
  • 相关阅读:
    vue中使用第三方UI库的移动端rem适配方案
    前端规范--eslint standard
    从上往下打印二叉树
    栈的压入,弹出序列
    随机森林
    LR
    顺时针打印矩阵
    包含min函数的栈
    树的子结构
    合并两个有序链表
  • 原文地址:https://www.cnblogs.com/little-uu/p/15020002.html
Copyright © 2020-2023  润新知