新的学期

ACM依然是我的热爱

怀着一颗热爱的心去学习,去训练

周一 3.1(KMP+Manacher)

回归编程

我一位很优秀的同学说

如果你做的事情是你热爱的,那么你就不会觉得累

acm就是我热爱的,写题学算法永远不会觉得累

用热爱驱动训练,加油

现在的大致规划是

先搞好周训的内容和提高篇那本书的内容

周训学号,那本书上的题基本做完

之后再去洛谷题单刷题

【模板】KMP字符串匹配

算是复习了

自己靠原理写出来了,有几个地方注意一下

1.求next数组是b的,不是a的

2.next数组的含义是next[i]表示0~i-1这一个字串中最长的前缀=后缀的长度,注意不包括i

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e6 + 10;
char a[N], b[N];
int Next[N], lena, lenb;void get_next()
{Next[0] = -1;int i = 0, j = -1; //注意一开始i为0,j为-1 为了让Next[1] = 0 也不会存在全串的情况 while(i < lenb){if(j == -1 || b[i] == b[j]) //是配b数组 {i++; j++;Next[i] = j;}else j = Next[j];}
}void kmp()
{int i = 0, j = 0;while(i < lena){if(j == -1 || a[i] == b[j]){i++; j++;if(j == lenb) printf("%d\n", i - j + 1);//i-j就是匹配的初始位置,是0开始算的 }                                           //=lenb后会重新匹配的,因为j越界了,不可能相等 else j = Next[j];}
}int main()
{scanf("%s%s", a, b);lena = strlen(a), lenb = strlen(b);get_next();kmp();REP(i, 0, lenb) printf("%d ", Next[i + 1]); //Next数组的含义 puts("");return 0;
}

P2375 [NOI2014] 动物园(kmp原理拓展)

独立做出蓝题

这题是道好题目

Next[i]可以理解为前i个字符,理解为下标也可以

首先要求前后缀相同而不重复的最长长度

我这里是求了两遍kmp

先正常的,第二遍加一个判断求出另外一个数组。直接暴力会超时,这样不超时

然后统计的时候记忆化一下防止超时就好了

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e6 + 10;
const int MOD = 1e9 + 7;
int Next[N], f[N], k[N], len;
char a[N];void get_next1()
{Next[0] = -1;int i = 0, j = -1;while(i < len){if(j == -1 || a[i] == a[j]){i++; j++;Next[i] = j; } else j = Next[j];}
}void get_next2()
{Next[0] = -1;int i = 0, j = -1;while(i < len){if((j == -1 || a[i] == a[j]) && (j + 1) * 2 <= i + 1){i++; j++;k[i] = j;    } else j = Next[j];}
}int num(int i)
{if(f[i]) return f[i];if(i <= 0) return 0;return f[i] = 1 + num(Next[i]);
}int main()
{int T; scanf("%d", &T);while(T--){scanf("%s", a);len = strlen(a);get_next1();get_next2();int ans = 1;memset(f, 0, sizeof(f));_for(i, 1, len) ans = ans * 1ll * (num(k[i]) + 1) % MOD;printf("%d\n", ans);}return 0;
}

P3805 【模板】manacher算法

同样理解算法原理

求一个字符串最大的回文子串长度

暴力O(n^2)这个算法可以优化到O(n)

和kmp有点像,都是利用之前已经匹配过的信息

首先我们需要一个对称中心,因为偶回文的对称中心是空隙,所以要全部转化为奇回文

于是加入#,同时两端加入不一样的字符防止越界,这就是初始化

初始化用string会好写一些

用p[i]表示以i为对称中心的最长回文串半径

我这个写法是不包括i本身的半径

对于之前求过的回文串最长右边界为mx

如果新的对称中心在mx左侧,就可以利用前面的信息确定一部分的回文串

这时注意不能超过mx-i,所以要取min

也就是利用前面的信息赋个初值,然后开始暴力判断,优化在这个赋初值这里

判断后我们要尽可能拓展mx,是的后面的i尽可能能赋初值

然后p[i]就是原串中以i这个位置为对称中心的的回文串长度,用它来更新ans

注意p数组要开字符串长度的两倍,因为字符串会初始化

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1.1e7 + 10;
string a, str;
int p[N << 1], len; int Manacher()
{int id = 0, mx = 0, ans = 0;len = str.size();REP(i, 1, len){if(i < mx) p[i] = min(p[2 * id - i], mx - i); //赋初值 else p[i] = 0;while(str[i - p[i] - 1] == str[i + p[i] + 1]) p[i]++; //暴力拓展 if(i + p[i] > mx) //尽可能拓展mx {mx = i + p[i];id = i;}ans = max(ans, p[i]);  //用p[i]更新答案 }  return ans;
} int main()
{ios::sync_with_stdio(0); //加速cin,写了之后要不全部用cout,要不全部用printf cin.tie(0);cin >> a;len = a.size();str = "&"; //str为初始化后的结果 REP(i, 0, len) str += "#", str += a[i];str += "#^"; cout << Manacher() << endl;return 0;
}

P1659 [国家集训队]拉拉队排练(回文串问题)

用Manacher算法求出以每个点为对称中心的回文串,可以说它是一个工具

然后统计答案就行,因为Manacher算的是最长回文串,对于p[i]来说,p[i] - 2, p[i] - 4都是答案

for一遍就好,注意要用快速幂

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e6 + 10;
const int MOD = 19930726;
string a, str;
int p[N << 1], cnt[N], n;
long long k;int mul(int a, int b) { return 1ll * a * b % MOD; }void Manacher()
{int id = 0, mx = 0, len = str.size();REP(i, 1, len){if(i < mx) p[i] = min(mx - i, p[2 * id - i]);else p[i] = 0;while(str[i - p[i] - 1] == str[i + p[i] + 1]) p[i]++;if(mx < i + p[i]){mx = i + p[i];id = i;}if(p[i] & 1) cnt[p[i]]++;}
}int binpow(int a, int b)
{int res = 1;for(; b; b >>= 1){if(b & 1) res = mul(res, a);a = mul(a, a); }return res;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cin >> n >> k >> a;str = "%";REP(i, 0, n) str += "#", str += a[i];str += "#@";Manacher();int ans = 1, t = 0;for(int i = n; i >= 1; i--)if(i & 1){if(cnt[i] + t <= k) {ans = mul(ans, binpow(i, cnt[i] + t));k -= cnt[i] + t;t += cnt[i];}else{ans = mul(ans, binpow(i, k));k = 0;break;}}if(k) puts("-1");else printf("%d\n", ans);return 0;
}

周二 3.2(Manacher+Tire树+AC自动机)

P4555 [国家集训队]最长双回文串(Manacher+堆)

这题写了一上午,一点一点想出,很爽

写完看了题解发现我的做法和别人的都不一样哈哈哈

首先用Manacher算法求出p[i],然后再每一个回文子串的左右端点标记一下,也就是代码中的L数组和R数组

很容易想到标记时长度更长肯定更优秀,所以我取了max

然后我就想到了一个O(n^2)的算法,枚举一个回文子串的右端点,再这个点前面枚举左端点

那么长度就是两个回文子串长度和-2 * 两个回文子串公共的部分,这个拿笔画一下就好了

那么显然要优化,不然会超时

式子len1 + len2 - 2 * 公共部分

len1是固定的,主要是后面两部分要最大

我发现当i++时,只有公共部分+2,而且是所有的都加2

所有都加2表示不同的len2它们的相对大小位置是不变的

我们要最优,那就要最大的

那么我们就可以用一个优先队列来维护

这里提一下,优先队列是大根堆,sort是从小到大。我以为优先队列小根堆卡了半天

用堆维护len2,后加入的要加上2,因为公共部分变了

这样就用一个堆优化成了nlogn了,主要是观察那个式子的特性

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e5 + 10;
string a, str;
int p[N << 1], L[N << 1], R[N << 1], len;struct node
{ int j, l;bool operator < (const node& rhs) const{return l < rhs.l;}
};
priority_queue<node> q;void Manacher()
{int id = 0, mx = 0; len = str.size();REP(i, 1, len){if(i < mx) p[i] = min(mx - i, p[2 * id - i]);else p[i] = 0;while(str[i + p[i] + 1] == str[i - p[i] - 1]) p[i]++;if(i + p[i] > mx){mx = i + p[i];id = i;}R[i + p[i]] = max(R[i + p[i]], p[i] * 2 + 1);L[i - p[i]] = max(L[i - p[i]], p[i] * 2 + 1);}
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cin >> a;len = a.size();str = "@";REP(i, 0, len) str += "#", str += a[i];str += "#!";Manacher();int ans = 0, t = 4;q.push(node{1, L[1]});q.push(node{2, L[2] + 2});_for(i, 2, len - 2){ans = max(ans, (R[i] - 1) / 2 - 1);if(i + 1 <= len - 2) q.push(node{i + 1, L[i + 1] + t});t += 2;while(!q.empty()){int j = q.top().j;if(j + (L[j] - 1) / 2 > i) break;q.pop();}if(!q.empty()){int j = q.top().j;ans = max(ans, (R[i] + L[j] - 2 * (i - j + 1)) / 2);}}printf("%d\n", ans);return 0;
}

复习nth_element

nth_element(a, a + x, a + n)

可以把第x大(第0大, 第1大)的元素放在a[x]

x前面的数都小于等于x,后面都大于等于x

可以将排序好后a[x]的值放在a[x],前面小于等于a[x]后面大于等于a[x]

Immediate Decodability(Trie树裸题)

做了一道裸题复习了一下Trie树

顺利写出,果然懂原理还是非常重要

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1100;
int t[N][2], vis[N], cnt, ans;
char s[15];void add(char* str)
{int len = strlen(str), p = 0;REP(i, 0, len){int id = str[i] - '0';if(i == len - 1 && t[p][id]) ans = 1;if(!t[p][id]) t[p][id] = ++cnt;p = t[p][id];if(vis[p]) ans = 1;}vis[p] = 1;
}int main()
{int kase = 0;while(~scanf("%s", s)){memset(t, 0, sizeof(t));memset(vis, 0, sizeof(vis));cnt = ans = 0;add(s);while(scanf("%s", s) && strcmp(s, "9")) add(s);if(ans) printf("Set %d is not immediately decodable\n", ++kase);else printf("Set %d is immediately decodable\n", ++kase);}return 0;
}

P3808 【模板】AC自动机(简单版)

通过好几个小时的研究

最后静下心来慢慢理解慢慢读,终于理解了AC自动机,不容易

但是理解还不深刻,需要做题来强化

实际上就是Trie树上跑kmp

kmp中是j = next[j],是一个串中跳

但是这里的转移就是所有串中最长的next,所以会在多个串中跳来跳去

kmp中一个串会匹配多次,AC自动机也一样,所以要避免重复计算

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e6 + 10;
int t[N][26], fail[N], End[N], n, cnt = 1; //cnt = 1写在全局,不要写到add
char s[N];void add(char* str)
{int p = 1, len = strlen(str); //p从1开始 REP(i, 0, len){int id = str[i] - 'a';if(!t[p][id]) t[p][id] = ++cnt;p = t[p][id];}End[p]++;
}void get_fail() //AC自动机主要过程。相当于把Trie树补全,后面查询时直接查
{_for(i, 0, 25) t[0][i] = 1; //注意有0节点,因为涉及到父亲的fail fail[1] = 0;           queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 25){if(t[u][i])  {q.push(t[u][i]); //有则加入 int v = fail[u];while(v && !t[v][i]) v = fail[v]; //类似求next数组中不匹配就一直J = next[j] fail[t[u][i]] = t[v][i]; //fail[t[u][i]] }else t[u][i] = t[fail[u]][i]; //值肯定存在,因为更浅的Trie都补全了 }                  //如果失配了,那么就跳到t[fail[u]][i]}
}int main()
{scanf("%d", &n);_for(i, 1, n){scanf("%s", s);add(s);}get_fail();scanf("%s", s);int len = strlen(s), p = 1, ans = 0;REP(i, 0, len) //Trie补全后直接遍历就好 {int id = s[i] - 'a';p = t[p][id];ans += End[p]; End[p] = 0; //避免重复计算,因为回跳可能像kmp跳回自己,后面又算一次 }printf("%d\n", ans);return 0;
}

周三 3.3 (AC自动机)

P3808 【模板】AC自动机(简单版)

发现昨天写的那个模板有问题

需要把所有后缀都统计一遍,同时防止重复

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e6 + 10;
int t[N][26], fail[N], End[N], n, len, cnt = 1;
char s[N];void add(char* str)
{int p = 1; len = strlen(str);REP(i, 0, len){int id = str[i] - 'a';if(!t[p][id]) t[p][id] = ++cnt;p = t[p][id];    } End[p]++; //注意重复单词
}void get_fail()
{_for(i, 0, 25) t[0][i] = 1;fail[1] = 0;queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 25){if(t[u][i]){q.push(t[u][i]);fail[t[u][i]] = t[fail[u]][i]; //t[fail[u]][i]不可能为0,一定指向正确的节点 }else t[u][i] = t[fail[u]][i];   //t[fail[u]][i]一定是指向一个确定的节点 }                                   //补全Tried树 }
}int main()
{scanf("%d", &n);_for(i, 1, n){scanf("%s", s);add(s);}get_fail();scanf("%s", s);int p = 1, ans = 0; len = strlen(s);REP(i, 0, len){p = t[p][s[i] - 'a'];for(int j = p; j > 1 && End[j] != -1; j = fail[j]) //遍历所有后缀 {ans += End[j];End[j] = -1;    } } printf("%d\n", ans);return 0;
}

Hdu 2222(AC自动机裸题)

End数组写++不要写=1

觉得题目没说清楚会不会有重复的单词,重复了怎么算

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 5e5 + 10;
const int M = 1e6 + 10;
int t[N][26], fail[N], End[N], n, len, cnt;
char s[M];void add(char* str)
{int p = 1; len = strlen(str);REP(i, 0, len){int id = str[i] - 'a';if(!t[p][id]) t[p][id] = ++cnt;p = t[p][id];    } End[p]++;
}void get_fail()
{_for(i, 0, 25) t[0][i] = 1;fail[1] = 0;queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 25){if(t[u][i]){q.push(t[u][i]);fail[t[u][i]] = t[fail[u]][i];}else t[u][i] = t[fail[u]][i];}}
}int main()
{int T; scanf("%d", &T);while(T--){cnt = 1;memset(t, 0, sizeof(t));memset(fail, 0, sizeof(fail));memset(End, 0, sizeof(End));scanf("%d", &n);_for(i, 1, n){scanf("%s", s);add(s);}get_fail();scanf("%s", s);int p = 1, ans = 0; len = strlen(s);REP(i, 0, len){p = t[p][s[i] - 'a'];for(int j = p; j > 1 && End[j] != -1; j = fail[j]){ans += End[j];End[j] = -1;}}printf("%d\n", ans);}return 0;
}

bzoj 4327(AC自动机)

用AC自动机可以遍历所有主串与模式串匹配的部分

每个p的位置到根的这个子串就是一个匹配的部分,这个串是主串的一个子串,也是某些模式串的前缀(或整个串)

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 4e5 + 10;
const int M = 1e7 + 10;
int t[N][4], fail[N], End[N], fa[N], vis[N], size[N];
int len, cnt, n, m;
char T[M], s[M];
map<char, int> id;void add(char* str, int i)
{int p = 1; len = strlen(str);REP(i, 0, len){int k = id[str[i]];if(!t[p][k]) t[p][k] = ++cnt;fa[t[p][k]] = p;p = t[p][k];}End[i] = p;
}void get_fail()
{_for(i, 0, 3) t[0][i] = 1;fail[1] = 0;queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 3){if(t[u][i]){q.push(t[u][i]);fail[t[u][i]] = t[fail[u]][i];}else t[u][i] = t[fail[u]][i];}}
}int main()
{cnt = 1;id['E'] = 0; id['S'] = 1;id['W'] = 2; id['N'] = 3;scanf("%d%d%s", &n, &m, T);_for(i, 1, m){scanf("%s", s);add(s, i);size[i] = strlen(s);}get_fail();int p = 1; len = strlen(T);vis[1] = 1;REP(i, 0, len){p = t[p][id[T[i]]];for(int j = p; j > 1 && !vis[j]; j = fail[j]) //遍历所有主串和模式串匹配的部分 {                                             //注意不要重复遍历 vis[j] = 1;while(!vis[fa[j]]) j = fa[j], vis[j] = 1;}}_for(i, 1, m){int t = End[i];if(vis[t]) printf("%d\n", size[i]);else{int ans = 0;while(!vis[fa[t]]) t = fa[t], ans++;printf("%d\n", size[i] - ans - 1);}}return 0;
}

bzoj 3940(AC自动机+栈)

之前做过一道kmp+栈,这里改成多个串就是AC自动机

思路是一样的,只不过实现方法不一样

这里有一点就是会重复遍历,容易超时

所以记忆化一下

不合法要返回-1,不要返回0,和没遍历过混淆

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e5 + 10;
int t[N][26], fail[N], End[N], k[N], d[N], f[N];
int len, n, cnt = 1;
char T[N], s[N]; void add(char* str)
{int p = 1, h = 0; len = strlen(str);REP(i, 0, len){int id = str[i] - 'a';if(!t[p][id]) t[p][id] = ++cnt;p = t[p][id];h++;}End[p] = 1;d[p] = h;
}void get_fail()
{_for(i, 0, 25) t[0][i] = 1;fail[1] = 0;queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 25){if(t[u][i]){q.push(t[u][i]);fail[t[u][i]] = t[fail[u]][i]; }   else t[u][i] = t[fail[u]][i];} }
}int find(int p)
{if(p <= 1) return -1;if(f[p]) return f[p];if(End[p]) return f[p] = p;return f[p] = find(fail[p]);
}int main()
{scanf("%s%d", T, &n);_for(i, 1, n){scanf("%s", s);add(s);}get_fail();int p = 1, top = 0; len = strlen(T);REP(i, 0, len){s[++top] = T[i];p = t[p][T[i] - 'a'];k[top] = p;int t = find(p);if(t > 1) top -= d[t], p = k[top];}_for(i, 1, top) printf("%c", s[i]); puts("");return 0;
}

周四 3.4(AC自动机)

今天能抽出的时间稍微少了一些

加上笔记本忘记充电智障了耽误了时间

不过我真的一天不碰键盘就手痒

晨跑习惯了,今天下雨没跑身体就不爽

保持下去

「一本通 2.4 练习 3」单词(AC自动机)

我发现AC自动机好多省选题,基本都是紫题

其实还好啦,算法本身理解比较难,但是理解后做题也就还好吧

这题的话一开始感觉比较奇怪,没有T串

首先建Trie ac自动机是肯定要的,然后咋整呢

我想到把论文当作T串,去匹配。AC自动机可以遍历所有匹配的情况,所以就用一个数组存一下匹配成功的次数

然后做完发现答案错的,原来是单词和单词之间不能连在一起

所以需要一个单词一个单词当作T串取匹配,每次匹配都重新开始

以为大概率超时,然后发现只超时了一个点

想了想,每次其实遍历很快,因为都重新开始了

超时地一个点应该是专门卡的

然后我就优化了一下,就是重复的单词合并在一起,就过了

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 1e6 + 10;
const int M = 210;
int t[N][26], fail[N], End[M], num[N], v[N];
int n, len, cnt = 1;
string s[M];void add(string s, int j)
{int p = 1; len = s.size();REP(i, 0, len){int id = s[i] - 'a';if(!t[p][id]) t[p][id] = ++cnt;p = t[p][id];}End[j] = p;
}void get_fail()
{_for(i, 0, 25) t[0][i] = 1;fail[1] = 0;queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 25){if(t[u][i]){q.push(t[u][i]);fail[t[u][i]] = t[fail[u]][i];}else t[u][i] = t[fail[u]][i];}}
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cin >> n;_for(i, 1, n){cin >> s[i];add(s[i], i);}get_fail();_for(i, 1, n){if(v[i] == -1) continue;v[i] = 1;_for(j, i + 1, n)if(s[i] == s[j])v[i]++, v[j] = -1;}_for(k, 1, n){if(v[k] == -1) continue;int p = 1; len = s[k].size();REP(i, 0, len){p = t[p][s[k][i] - 'a'];for(int j = p; j > 1; j = fail[j]) num[j] += v[k];}}_for(i, 1, n) cout << num[End[i]] << endl;return 0;
}

周六 3.6(AC自动机)

昨天没记录是因为写一道题想了很久没想出来

今天又想了很久,最后看了题解

P2322 [HNOI2006]最短母串问题(AC自动机+状态压缩+bfs求最短路)

这题还是很精彩的

我自己做的时候看到n=12 想到的是搜索,但又有点虚怕会超时

就是搜索枚举所有情况,一些记录注意回溯时恢复现场

最后果然60分超时了

然后就没思路了

看题解发现用到状态压缩的方法

这个知识点我太不熟练了,状压dp我之后会好好搞它一遍

所以做题要独立思考,思考了足够长时间就可以看题解,可能是有些知识点或者算法你不知道

n <= 12也暗示了状态压缩

首先很容易想到就是求一条最短路径使得遍历了Trie树上所有的字符串的终点

那么可以把目前经历了哪些字符串用二进制存一个状态

在AC自动机时可以预处理,把当前节点的fail的状态也合并进来

然后就开始bfs,按照字典序拓展,最先达到经历了所有字符串状态的就是答案

bfs的时候记得去掉重复状态,后面重复的状态肯定不是答案,时间优化

然后记录答案的时候可以用ans 和fa数组来存,不用存字符串消耗空间

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 610;
int t[N][26], fail[N], state[N], fa[N * (1 << 12)], vis[N][1 << 12];
int len, n, cnt = 1;
struct node{ int u, s; };
vector<char> ans;
string s;void add(string str, int i)
{int p = 1; len = str.size();REP(i, 0, len){int id = str[i] - 'A';if(!t[p][id]) t[p][id] = ++cnt;p = t[p][id];}state[p] |= 1 << (i - 1);
}void get_fail()
{_for(i, 0, 25) t[0][i] = 1;fail[1] = 0;queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 25){if(t[u][i]){q.push(t[u][i]);fail[t[u][i]] = t[fail[u]][i];state[t[u][i]] |= state[fail[t[u][i]]];}else t[u][i] = t[fail[u]][i];}}
}void print(int x)
{if(x == 0) return;print(fa[x]);cout << ans[x];
} int main()
{ios::sync_with_stdio(0);cin.tie(0);cin >> n;_for(i, 1, n){cin >> s;add(s, i);}get_fail();queue<node> q;q.push(node{1, 0});int id = 0;ans.push_back(' ');while(!q.empty()){node x = q.front(); q.pop();int u = x.u, s = x.s;if(s == (1 << n) - 1){print(id);break;}_for(i, 0, 25)if(!vis[t[u][i]][s | state[t[u][i]]]){vis[t[u][i]][s | state[t[u][i]]] = 1;q.push(node{t[u][i], s | state[t[u][i]]});ans.push_back(i + 'A');fa[ans.size() - 1] = id;}id++;}return 0;
}

周日 3.7 (AC自动机)

昨晚打了第一场积分赛

我太想拿好成绩,从而太紧张了,根本不能静下心来想问题,导致发挥失常

一直犯一些低级错误

我虽然训练了挺久,但是比赛经验还是太少

今晚又有一场

要能静下心来想问题,合理规划时间

比赛时的心理素质也是非常重要的一个方面

「一本通 2.4 练习 5」病毒(AC自动机+dfs+避免重复遍历)

这题显然就是在Trie树上跑,在不经过字符串终点的情况看是否能循环

Trie树上走过一个节点时不仅包含了当前根到此节点的字符串,还包含一些其他字符串,这些字符串是此节点字符串的后缀,也就是fail

一般会在初始化的时候延续fail的某些信息,或者在统计的时候遍历fail

这里是在初始化的时候延续

做dfs就好

需要注意的是,一般题目中dfs不会重复遍历一个节点,但是这道题会,在Trie树上跳可能又跳到同一个节点

所以要判重,不要重复遍历。判重就回溯的时候处理一下就好。

不然会T掉2个点

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 3e4 + 10;
int t[N][2], fail[N], vis[N], End[N];
int len, n, cnt = 1;
string s;void add(string str)
{int p = 1; len = str.size();REP(i, 0, len){int id = str[i] - '0';if(!t[p][id]) t[p][id] = ++cnt;p = t[p][id];}End[p] = 1;
}void get_fail()
{_for(i, 0, 1) t[0][i] = 1;fail[1] = 0;queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 1){if(t[u][i]){q.push(t[u][i]);fail[t[u][i]] = t[fail[u]][i];End[t[u][i]] |= End[fail[t[u][i]]]; //延续信息 }else t[u][i] = t[fail[u]][i];}}
}bool dfs(int u)
{_for(i, 0, 1){if(End[t[u][i]] || vis[t[u][i]] == 2) continue;if(vis[t[u][i]] == 1) return true;vis[t[u][i]] = 1;if(dfs(t[u][i])) return true;vis[t[u][i]] = 2; //避免重复遍历 }return false;
}int main()
{ios::sync_with_stdio(0);cin.tie(0);cin >> n;_for(i, 1, n) cin >> s, add(s);get_fail();if(dfs(1)) puts("TAK");else puts("NIE");return 0;
}

P3796 【模板】AC自动机(加强版)(AC自动机+树形dp)

这题就是要统计每个模式串出现的次数

暴力跳肯定超时,考虑怎么优化

首先那些对答案不产生贡献的点可以略去,所以要修改一下fail数组

其次先标记,然后做一遍树形dp

我自己的做法就是按照深度,从最深的开始,每一个高度的都往它的fail加一次,这样统计是正确的,复杂度也是O(n)

有点像倒序的bfs

本质上应该和树形dp差不多

#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;const int N = 10500 + 10;
int t[N][26], fail[N], End[N], num[N], len, n, cnt = 1;
string s[160], T;
vector<int> ve[80];void add(string str, int i)
{int p = 1, h = 0; len = str.size();REP(i, 0, len){h++;int id = str[i] - 'a';if(!t[p][id]) {t[p][id] = ++cnt;ve[h].push_back(t[p][id]);}p = t[p][id];}End[i] = p;
}void get_fail()
{_for(i, 0, 25) t[0][i] = 1;fail[1] = 0;queue<int> q;q.push(1);while(!q.empty()){int u = q.front(); q.pop();_for(i, 0, 25){if(t[u][i]){q.push(t[u][i]);fail[t[u][i]] = t[fail[u]][i];}else t[u][i] = t[fail[u]][i];}}
}int main()
{ios::sync_with_stdio(0);cin.tie(0);while(cin >> n && n){cnt = 1;memset(t, 0, sizeof(t));memset(num, 0, sizeof(num));_for(i, 1, 75) ve[i].clear();_for(i, 1, n) cin >> s[i], add(s[i], i);get_fail();cin >> T;len = T.size();int p = 1;REP(i, 0, len){p = t[p][T[i] - 'a'];num[p]++;}for(int i = 75; i >= 1; i--)for(auto u: ve[i])num[fail[u]] += num[u];int mx = 0;_for(i, 1, n)mx = max(mx, num[End[i]]);cout << mx << endl;_for(i, 1, n)if(num[End[i]] == mx)cout << s[i] << endl;}return 0;
}

大一下第一周学习笔记相关推荐

  1. 机电传动控制课程第一周学习笔记

    机电传动课程第一周学习笔记 本周的学习内容主要是第一章绪论和第二章机电传动系统的动力学基础,结合课程学习和预习复习回顾内容如下: 1.绪论:学习了机电传动控制目的与任务.发展历程和我们该如何学习这门课 ...

  2. 机电传动控制第一周学习笔记

    机电传动控制第一周学习笔记: 1 这一周主要讲述了概论和机电传动控制系统动力学基础两个章节内容. 2 绪论中说明了<机电传动控制>课程主要内容为下图所示: 3机电传动控制系统动力学基础章节 ...

  3. HTML第一周学习笔记(标题重置版)

    适合新手小白的HTML网页编辑 前 言 一.HTML简介基本元素组成 二.实操重点信息 1.引入链接 2.定义数据 3.图片插入 总结 前 言 博客学习记录于3月4日星期四完成上传编辑,作为刚刚新手小 ...

  4. 20135320赵瀚青LINUX内核分析第一周学习笔记

    赵瀚青原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.概述 第一周的学习内容主 ...

  5. 吴恩达深度学习 | (20) 序列模型专项课程第一周学习笔记

    课程视频 第一周PPT汇总 吴恩达深度学习专项课程共分为五个部分,本篇博客将介绍第五部分序列模型专项的第一周课程:循环序列模型. 目录 1. 为什么选择序列模型? 2. 数学符号 3. 循环神经网络模 ...

  6. 吴恩达机器学习公开课第一周学习笔记

    Octave是一种编程语言,旨在解决线性和非线性的数值计算问题.Octave为GNU项目下的开源软件,早期版本为命令行交互方式,4.0.0版本发布基于QT编写的GUI交互界面.Octave语法与Mat ...

  7. 大数据第一阶段学习笔记

    开始:2022年11月6日 以下内容仅为个人笔记整理.(第一阶段的内容并不完全.硬件上有点问题,暂时无法解决,空着的部分后续补上.) 第0章 大数据介绍 大数据可以从事的职位有: 大数据工程师 数据分 ...

  8. 机器学习基础-吴恩达-coursera-(第一周学习笔记)----Introduction and Linear Regression

    课程网址:https://www.coursera.org/learn/machine-learning Week 1 -- Introduction and Linear Regression 目录 ...

  9. 面向对象高级编程(上)-- 第一周学习笔记(Boolan)

    培养正规的.大气的编程习惯 一.C++简介 1,分类 C++分为基于对象和面向对象两种 基于对象是指简单的类定义,数据封装,没有类与类的联系,面对的是单一class的设计.又可细分为不带指针的类和带指 ...

最新文章

  1. Travis CI : 最小的分布式系统(一)
  2. javaweb学习总结三(枚举)
  3. 为什么ui框架设计成单线程_评估UI设计的备忘单
  4. 框架学习与探究之AOP--Castle DynamicProxy
  5. mysql sql注入怎么获取数据_手把手教你通过SQL注入盗取数据库信息
  6. 机器视觉中使用光源及偏振镜解决物体反光问题
  7. LR中并发用户和集合点
  8. 2020年只有外包公司给面试机会, Why?
  9. Python使用scrapy框架编写自动爬虫爬取京东商品信息并写入数据库
  10. IDEA 运行 Tomcat 中文乱码的各种问题
  11. 液晶拼接大屏的日常维护与保养
  12. 学渣笔记——Java常用基本数据类型
  13. 使用iMazing给苹果手机设置专属来电铃声
  14. Mac下Go的安装与配置
  15. git 提交报错 Incorrect username or password ( access token )
  16. Gimy 剧迷更新快,内容超多的电影、美日韩剧、动漫片源
  17. ros中的电机速度控制_ROS与duckietbot指南-线速度和角速度校准
  18. STM32之system_stm32f4xx.c的理解
  19. MySQL更新数据流程
  20. Python3相对路径符号斜杠 (/),点斜杠(./),点点斜杠(../)的意思

热门文章

  1. 创建Chinaskills为GPO管理员;加入到企业管理、域控管理员组
  2. 分布式(网站架构演化)
  3. 实用宝典 | 如何用Python实现人机猜拳小游戏
  4. Shopee新手卖家怎么做好店铺运营之店铺广告投放
  5. i5 12400性能怎么样 酷睿i512400核显相当于什么水平
  6. 为让代码注释更和谐,有人提议用拥抱替换 Fu*k
  7. 函数生成的ALV的按钮列表
  8. excel中如何截取指定字符之间的字符
  9. BugKu-杂项(Misc)的部分Writeup(持续更新,直到刷完)
  10. 数据结构--宿舍管理查询软件