「学习笔记」字符串基础:Hash,KMP与Trie

点击查看目录

Hash

算法

为了方便处理字符串,我们可以把一个字符串转化成如下形式(\(s\) 为原字符串,\(l\) 为字符串长度,\(b\) 为进制,\(P\) 为防止溢出的大质数):

\[\operatorname{Hash}(s)=\sum_{i=1}^{l}s_i*b^{l-i}\bmod{P}
\]

其中,\(b\) 一般选取一个三位质数即可(建议用 \(233\)),而 \(P\) 一般要选一个大质数。

我们可以直接通过比较两个字符串的哈希值是否相等来比较它们是否一样,但是显然两个不一样字符串的哈希值也是有很小的概率相等的,这种 \(\operatorname{Hash}\) 函数值一样时原字符串却不一样的现象我们称为哈希碰撞

我们可以通过双模数来降低哈希碰撞的概率,但是要注意尽量不要选取常用的质数(如 \(10^9+7,10^9+9,998244353\)),否则出题人会故意卡你。

我的建议是背几个不常用的质数(如 \(315716251,475262633\)),或者考场上现造质数。

咋现造啊

Linux 终端有个指令叫 factor,分解质因数用的。

乱写一堆数然后分解一下选几个比较大的不越界的质数(一般用九位数就行,如果是哈希表这样用来开数组的选个八位数)即可。

嗯上边那俩质数就是这样干出来的。

处理一整个字符串的前缀的时间复杂度是 \(\Theta(\left|S\right|)\),直接用 \(\left(\operatorname{Hash}(i-1)+s_i*b^{i-1}\right)\) 得到 \(\operatorname{Hash}(i)\)即可。

如何快速求 \(S_l\sim S_r\) 的哈希值?直接用 \(\left(\operatorname{Hash}(r)-\operatorname{Hash}(l-1)\times b^{r-l+1}\right)\) 就可以算出,时间复杂度是 \(\Theta(1)\)。

代码

点击查看代码
  1. class Hash{
  2. public:
  3. const ll P1=315716251,P2=475262633;
  4. ll h1[N],h2[N],z1[N],z2[N];
  5. inline void Init(char *s,ll k){
  6. ll len=strlen(s+1);
  7. ll b=233;
  8. z1[0]=z2[0]=1;
  9. _for(i,1,len){
  10. z1[i]=z1[i-1]*b%P1;
  11. z2[i]=z2[i-1]*b%P2;
  12. h1[i]=(h1[i-1]*b+(s[i]-'A'+1)%P1)%P1;
  13. h2[i]=(h2[i-1]*b+(s[i]-'A'+1)%P2)%P2;
  14. }
  15. return;
  16. }
  17. inline ll GetHash1(ll l,ll r){return (h1[r]-h1[l-1]*z1[r-l+1]%P1+P1)%P1;}
  18. inline ll GetHash2(ll l,ll r){return (h2[r]-h2[l-1]*z2[r-l+1]%P2+P2)%P2;}
  19. }a,b;

KMP

算法

(这里的代码都是直接从模板题粘过来的)

前置知识:\(\text{Border}\)

思路

先来几个定义:

  • \(Pre_i\) 一个字符串长为 \(i\) 的前缀。

  • 一个字符串 \(s\) 的 \(\text{Border}\) 为一个同时是 \(s\) 前后缀的真子串。

    例如 ababab 就是 ababab 的 \(\text{Border}\),而 abaa 则不是 。

  • \(nxt_i\) 表示当前字符串长为 \(i\) 的前缀最长的 \(\text{Border}\) 的长度。

\(nxt_i\) 数组的应用十分广泛,\(\text{KMP}\) 只是其中的应用之一。

这个数组可以 \(\Theta(n^2)\) 求,但效率太低了,那么如何 \(\Theta(n)\) 求这个东西呢?

首先给出一个字符串 \(s\),我们假设之前的 \(nxt_{1}\sim nxt_{11}\) 都已经求完了,现在要求 \(nxt_{12}\)(最后的那个)。

  1. abcabdabcabc
  2. 00012012345?

\(nxt_{11}=5\),可以看出这个 \(\text{Border}\) 是 abcab

  1. abcab d abcab | c
  2. [---]
  3. [---] |

我们尝试把它往后推一位,但是 \(s_6\neq s_{12}\),也就是说失配了!

这个时候我们看一眼 \(nxt_{nxt_{11}}\),即 \(nxt_5=2\),\(\text{Border}\) 是 ab

  1. abcab | dabcabc
  2. [] |
  3. [] |

由于 \(s_1\sim s_5=s_7\sim s_{11}\),我们可以把 \(s_1\sim s_5\) 的 \(\text{Border}\) 推广到 \(s_7\sim s_{11}\)上,即 \(s_7\sim s_8=s_{10}\sim s_{11}\)

  1. abcabdabcabc
  2. []-[]
  3. []-[]

\(s_1\sim s_5=s_7\sim s_{11}\),所以 \(s_1\sim s_2=s_{7}\sim s_{8}\) 且 \(s_4\sim s_5=s_{10}\sim s_{11}\),即 \(s_1\sim s_2=s_{10}\sim s_{11}\)

  1. abcabdabcabc
  2. []
  3. []

这个时候我们再继续往后推,可以发现 \(s_3\neq s_{12}\),能够匹配上了!

那么最后算出 \(nxt_{12}=3\)。

  1. abcabdabcabc
  2. [-]
  3. [-]

最后总结一下求 \(nxt_{i}\) 的步骤:

  1. 令 \(j=nxt_{i-1}\)。

  2. 尝试匹配 \(s_{j+1}\) 和 \(s_i\) 如果失配则令 \(j=nxt_{j}\) 并不断循环此步,直到 \(j=0\) 或匹配成功。

  3. 令 \(nxt_i=j+[s_{j+1}=s_i]\)。

代码
  1. inline void PreNxt(){
  2. nxt[1]=0;ll k=0;
  3. _for(i,2,m){
  4. while(k&&t[i]!=t[k+1])k=nxt[k];
  5. k+=(t[i]==t[k+1]),nxt[i]=k;
  6. }
  7. return;
  8. }

\(\text{KMP}\) 匹配

$\text{Hot Knowlegde}$

该算法由 Knuth、Pratt 和 Morris 在 1977 年共同发布

[1]

思路

定义:

  • 模式串是要查找的串。
  • 文本串是被查找的串。

\(\text{KMP}\) 算法的思路其实和求 \(nxt\) 数组差不多,如果当前两个串失配了,那么我们在模式串中不断跳 \(nxt_{nxt_{nxt_{\cdots}}}\),直到匹配成功再继续往下。

(个人认为 \(nxt\) 数组更像一个指针,它指向位置的是失配后重新匹配的最优位置)

代码

下面这份代码中,文本串是 \(s\),模式串是 \(t\),在运行前需要已经求出 \(nxt\) 数组,输出的是模式串在文本串中每次出现的起始位置。

  1. inline void Matching(){
  2. ll k=0;
  3. _for(i,1,n){
  4. while(k&&s[i]!=t[k+1])k=nxt[k];
  5. k+=(s[i]==t[k+1]);
  6. if(k==m){
  7. printf("%lld\n",i-m+1);
  8. k=nxt[k];
  9. }
  10. }
  11. }

Trie

TRIE=sTRIng+TReE

数据结构

Trie 就是把一堆字符串放到树上方便查找。

例如我们插入以下几个单词

  1. apple
  2. cat
  3. copy
  4. coffee

就会长出这样一棵 Trie:

好像有点丑 确实有点丑。

01-Trie

指字符集为 \(\{0,1\}\) 的 Trie。

把数字换成二进制后塞到 Trie 里就得到了 01-Trie。

01-Trie 有很多应用,例如维护异或极值。

代码

  1. class Trie{
  2. private:
  3. ll tot=1;
  4. class{public:ll nx[26];bool end;}tr[N];
  5. public:
  6. inline void Add(char *s){
  7. ll len=strlen(s+1),p=1;
  8. _for(i,1,len){
  9. if(!tr[p].nx[s[i]-'a'])tr[p].nx[s[i]-'a']=++tot;
  10. p=tr[p].nx[s[i]-'a'];
  11. }
  12. tr[p].end=1;
  13. return;
  14. }
  15. inline bool Find(char *s){
  16. ll len=strlen(s+1),p=1;
  17. _for(i,1,len){
  18. if(!tr[p].nx[s[i]-'a'])return 0;
  19. p=tr[p].nx[s[i]-'a'];
  20. }
  21. return tr[p].end;
  22. }
  23. }tr;

练习题

Hash

Bovine Genomics

思路

可以枚举左右端点暴力比较,确定好左右端点和要比较的两个字符串之后可以 \(\Theta(1)\) 哈希比较,但显然总复杂度 \(\Theta(n^4)\) 跑不动,考虑如何优化。

这道题让我们求最短区间长度,那么我们考虑二分答案这个长度

但这样复杂度是 \(\Theta(n^3\log_2n)\) 的,依旧跑不动。

可以发现我没有必要将所有字符串都匹配一遍,只需要判断当前这个区间有没有重复出现,那么我们直接把哈希值丢进一个 map 里面维护即可,复杂度 \(\Theta(n^2\log_2^2n)\)。

但是不能直接把字符串丢进 map 里,因为这种情况 map 不是现哈希就是用 Trie,单次更改/查询复杂度都是 \(\Theta(n)\)。

代码
点击查看代码
  1. const ll N=510,inf=1ll<<40;
  2. ll n,m,ans,le,ri;
  3. char s[N][N],t[N][N];
  4. map<ll,bool>mp1,mp2;
  5. class Hash{
  6. public:
  7. const ll P1=315716251,P2=475262633;
  8. ll h1[N],h2[N],z1[N],z2[N];
  9. inline void Init(char *s){
  10. ll len=strlen(s+1);
  11. z1[0]=z2[0]=1;
  12. _for(i,1,m){
  13. z1[i]=z1[i-1]*233%P1;
  14. z2[i]=z2[i-1]*233%P2;
  15. h1[i]=(h1[i-1]*233+(s[i]-'A'+1)%P1)%P1;
  16. h2[i]=(h2[i-1]*233+(s[i]-'A'+1)%P2)%P2;
  17. }
  18. return;
  19. }
  20. inline ll GetHash1(ll l,ll r){return (h1[r]-h1[l-1]*z1[r-l+1]%P1+P1)%P1;}
  21. inline ll GetHash2(ll l,ll r){return (h2[r]-h2[l-1]*z2[r-l+1]%P2+P2)%P2;}
  22. }a[N],b[N];
  23. namespace SOLVE{
  24. inline ll rnt(){
  25. ll x=0,w=1;char c=getchar();
  26. while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
  27. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
  28. return x*w;
  29. }
  30. inline bool Check(ll len){
  31. _for(i,len,m){
  32. bool bl=1;
  33. mp1.clear(),mp2.clear();
  34. _for(j,1,n){
  35. mp1[a[j].GetHash1(i-len+1,i)]=1;
  36. mp2[a[j].GetHash2(i-len+1,i)]=1;
  37. }
  38. _for(j,1,n){
  39. if(mp1[b[j].GetHash1(i-len+1,i)])bl=0;
  40. if(mp2[b[j].GetHash2(i-len+1,i)])bl=0;
  41. }
  42. if(bl)return 1;
  43. }
  44. return 0;
  45. }
  46. inline void In(){
  47. n=rnt(),m=rnt();
  48. _for(i,1,n)scanf("%s",s[i]+1),a[i].Init(s[i]);
  49. _for(i,1,n)scanf("%s",t[i]+1),b[i].Init(t[i]);
  50. ll l=1,r=m;
  51. while(l<r){
  52. bdmd;
  53. if(Check(mid))r=mid;
  54. else l=mid+1;
  55. }
  56. printf("%lld\n",l);
  57. return;
  58. }
  59. }

[TJOI2018]碱基序列

思路

设 \(t_{i,j}\) 是 \(i\) 个氨基酸的第 \(j\) 种,\(f_{i,j}\) 表示第 \(i\) 个氨基酸的结尾放在 \(j\) 的方案数,则转移方程为:

\[f_{i,j}=\sum_{i=\left|t_{i,j}\right|}^{\left|S\right|}[S_{i-\left|t_{i,j}\right|+1}\sim S_i=\!=t_{i,j}]
\]

数组可以滚一下,但感觉没什么必要。

比较直接哈希,时间复杂度 \(\Theta(ka_i\left|S\right|)\)。

代码
点击查看代码
  1. const ll N=1e4+10,P=1e9+7,inf=1ll<<40;
  2. ll n,a[N],l,f[110][N],ans;
  3. char s[N],t[N];
  4. vector<ll>pp;
  5. class Hash{
  6. public:
  7. const ll P1=315716521,P2=475262633;
  8. ll h1[N],h2[N],z1[N],z2[N];
  9. inline void Init(char *s){
  10. z1[0]=z2[0]=1;
  11. ll length=strlen(s+1);
  12. _for(i,1,length){
  13. z1[i]=z1[i-1]*233%P1;
  14. z2[i]=z2[i-1]*233%P2;
  15. h1[i]=(h1[i-1]*233+(s[i]-'A'+1)%P1)%P1;
  16. h2[i]=(h2[i-1]*233+(s[i]-'A'+1)%P2)%P2;
  17. }
  18. return;
  19. }
  20. inline ll GetHash1(ll l,ll r){return (h1[r]-h1[l-1]*z1[r-l+1]%P1+P1)%P1;}
  21. inline ll GetHash2(ll l,ll r){return (h2[r]-h2[l-1]*z2[r-l+1]%P2+P2)%P2;}
  22. }d;
  23. namespace SOLVE{
  24. inline ll rnt(){
  25. ll x=0,w=1;char c=getchar();
  26. while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
  27. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
  28. return x*w;
  29. }
  30. inline ll GetS1(char *s){
  31. ll s1=0;
  32. _for(i,1,strlen(s+1))s1=(s1*233+s[i]-'A'+1)%d.P1;
  33. return s1;
  34. }
  35. inline ll GetS2(char *s){
  36. ll s2=0;
  37. _for(i,1,strlen(s+1))s2=(s2*233+s[i]-'A'+1)%d.P2;
  38. return s2;
  39. }
  40. inline void In(){
  41. n=rnt();
  42. scanf("%s",s+1);
  43. l=strlen(s+1);d.Init(s);
  44. _for(i,1,n){
  45. ll a=rnt();
  46. _for(j,1,a){
  47. scanf("%s",t+1);
  48. ll len=strlen(t+1);
  49. ll s1=GetS1(t),s2=GetS2(t);
  50. _for(k,len,l){
  51. if(d.GetHash1(k-len+1,k)!=s1)continue;
  52. if(d.GetHash2(k-len+1,k)!=s2)continue;
  53. f[i][k]=(f[i][k]+f[i-1][k-len])%P;
  54. if(i==1&&!f[i-1][k-len])++f[i][k];
  55. }
  56. }
  57. }
  58. _for(i,1,l)ans=(ans+f[n][i])%P;
  59. printf("%lld\n",ans);
  60. return;
  61. }
  62. }

[CQOI2014]通配符匹配

题解在这里

[NOI2017] 蚯蚓排队

思路

观察数据范围可以发现 \(k\le50\)。

那么就可以暴力去跑了,简单来说:

  • 对于操作一:把在拼接处新拼接出的所有向后 \(k\) 字符串(\(1\le k\le50\))记录。

  • 对于操作二:把在断开处层拼接出的所有向后 \(k\) 字符串(\(1\le k\le50\))去除。

  • 对于操作三:暴力枚举 \(S\) 的每个长度为 \(k\) 的子串,累计出现次数。

那么如何记录呢?再看题面还可以发现我们不用去逐位比较,只要记录每个串出现次数即可。

我刚开始用的是 map,但自带一个 \(\log\) 会超时。

那么我们引入一个东西叫 哈希表,简单来说就是先对原串哈希值取个模得到 \(x\),然后开个链表存取模后得到 \(x\) 的所有哈希值。查找时直接把存取模后得到 \(x\) 的所有哈希值的链表全部遍历一遍来查找。因为哈希冲突概率本来就很小,所以用不了查找多久。

时间复杂度 \(\Theta(km+\sum\left|S\right|)\)。

代码
点击查看代码
  1. const ll N=1e7+10,MOD=998244353,P=19491001,inf=1ll<<40;
  2. ll n,m,ans,arry[110],ss[N];ull z[110],h[110];char t[N];
  3. class lb{public:ll la,nx,va;}lb[N];
  4. namespace Hash{
  5. class HashTable{
  6. public:
  7. ll tot=0,hd[P]={0};
  8. class Table{
  9. public:
  10. ull hv;ll cnt,nx;
  11. inline void Add(ull a,ll b,ll c){hv=a,cnt=b,nx=c;}
  12. }t[P*2];
  13. inline void Add(ull val){
  14. ll v=val%P;ll f=hd[v];
  15. while(f&&t[f].hv!=val)f=t[f].nx;
  16. if(!f)t[++tot].Add(val,1,hd[v]),hd[v]=tot;
  17. else ++t[f].cnt;
  18. return;
  19. }
  20. inline void Del(ull val){
  21. ll v=val%P;ll f=hd[v];
  22. while(f&&t[f].hv!=val)f=t[f].nx;
  23. if(f)--t[f].cnt;
  24. return;
  25. }
  26. inline ll Que(ull val){
  27. ll v=val%P;ll f=hd[v];
  28. while(f&&t[f].hv!=val)f=t[f].nx;
  29. if(f)return t[f].cnt;
  30. return 0;
  31. }
  32. }ha;
  33. inline void Merge(ll a,ll b){
  34. ll st=a,en=a,le=1,ri=0;
  35. lb[a].nx=b,lb[b].la=a;
  36. while(lb[st].la!=-1&&le<50)st=lb[st].la,++le;
  37. _for(i,1,le)h[i]=h[i-1]*131+lb[st].va,st=lb[st].nx;
  38. while(lb[en].nx!=-1&&ri<=50)en=lb[en].nx,++ri,h[le+ri]=h[le+ri-1]*131+lb[en].va;
  39. _for(i,1,le)_for(j,le+1,min(ri+le,i+49))ha.Add(h[j]-h[i-1]*z[j-i+1]);
  40. return;
  41. }
  42. inline void Divition(ll k){
  43. ll st=k,en=k,le=1,ri=0;
  44. while(lb[st].la!=-1&&le<50)st=lb[st].la,++le;
  45. _for(i,1,le)h[i]=h[i-1]*131+lb[st].va,st=lb[st].nx;
  46. while(lb[en].nx!=-1&&ri<=50)en=lb[en].nx,++ri,h[le+ri]=h[le+ri-1]*131+lb[en].va;
  47. _for(i,1,le)_for(j,le+1,min(ri+le,i+49))ha.Del(h[j]-h[i-1]*z[j-i+1]);
  48. lb[lb[k].nx].la=-1,lb[k].nx=-1;
  49. return;
  50. }
  51. inline ll Query(ll k,ll len){
  52. ll ans=1;ull val=0;
  53. _for(i,1,len){
  54. if(i>k)val-=z[k-1]*ss[i-k];
  55. val=val*131+ss[i];
  56. if(i>=k)ans=ans*ha.Que(val)%MOD;
  57. }
  58. return ans;
  59. }
  60. }
  61. using namespace Hash;
  62. namespace SOLVE{
  63. char buf[1<<20],*p1,*p2;
  64. #define gc()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
  65. inline ll rnt(){
  66. ll x=0,w=1;char c=gc();
  67. while(!isdigit(c)){if(c=='-')w=-1;c=gc();}
  68. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=gc();
  69. return x*w;
  70. }
  71. inline void In(){
  72. n=rnt(),m=rnt(),z[0]=1;
  73. _for(i,1,100)z[i]=z[i-1]*131;
  74. _for(i,1,n){
  75. lb[i].va=rnt()+'0';
  76. lb[i].nx=lb[i].la=-1;
  77. Hash::ha.Add(lb[i].va);
  78. }
  79. _for(i,1,m){
  80. ll op=rnt();
  81. if(op==1){
  82. ll x=rnt(),y=rnt();
  83. Merge(x,y);
  84. }
  85. else if(op==2){
  86. ll x=rnt();
  87. Divition(x);
  88. }
  89. else{
  90. char c=gc();ll t=0;
  91. while(!isdigit(c))c=gc();
  92. while(isdigit(c))ss[++t]=c,c=gc();
  93. ll k=rnt();
  94. printf("%lld\n",Query(k,t));
  95. }
  96. }
  97. return;
  98. }
  99. }

KMP

Seek the Name, Seek the Fame

思路

板子题,因为 \(\text{Border}\) 的 \(\text{Border}\) 还是 \(\text{Border}\),所以一路 \(nxt\) 下去就可以了。

代码
点击查看代码
  1. inline void PreNxt(){
  2. nxt[0]=0;ll j=0;
  3. _for(i,2,n){
  4. while(j&&s[i]!=s[j+1])j=nxt[j];
  5. if(s[i]==s[j+1])++j;
  6. nxt[i]=j;
  7. }
  8. return;
  9. }
  10. void Print(ll i){
  11. if(i<1)return;
  12. Print(nxt[i]-(bool)(nxt[i]==i));
  13. printf("%lld ",i);
  14. return;
  15. }
  16. inline void In(){
  17. n=strlen(s+1);
  18. PreNxt(),Print(n),puts("");
  19. return;
  20. }

[NOI2014] 动物园

思路

题目要求不重叠的 \(\text{Border}\) 数量,那么我们可以先求出一个 \(cnt\) 数组表示 可重叠的 \(\text{Border}\) 数量。

不难发现我们找到一个长度不超过 \(\tfrac{k}{2}\) 的最长的 \(\text{Border}\):\(Pre_k\),则 \(num_i=cnt_k\)。

而 \(cnt\) 的求法也很简单:\(cnt_i=cnt_{nxt_i}+1\),求 \(nxt\) 时顺便求一下就行了。

代码

代码里的 num 就是 cnt

点击查看代码
  1. inline void PreNxt(){
  2. nxt[1]=0,num[1]=1;ll j=0;
  3. _for(i,2,n){
  4. while(j&&s[i]!=s[j+1])j=nxt[j];
  5. if(s[i]==s[j+1])++j;
  6. nxt[i]=j,num[i]=num[j]+1;
  7. }
  8. return;
  9. }
  10. inline void SolveNum(){
  11. ans=1;ll k=0;
  12. _for(i,2,n){
  13. while(k&&s[i]!=s[k+1])k=nxt[k];
  14. if(s[i]==s[k+1])++k;
  15. while(k&&(k<<1)>i)k=nxt[k];
  16. ans=ans*(num[k]+1)%P;
  17. }
  18. return;
  19. }
  20. inline void In(){
  21. scanf("%s",s+1);
  22. n=strlen(s+1);
  23. PreNxt(),SolveNum();
  24. printf("%lld\n",ans);
  25. return;
  26. }

[USACO15FEB] Censoring S

思路

本题的难点在于如何删除一个子串。

考虑用一个栈 st 维护当前剩下的串,每次匹配成功后把这个匹配成功的子串弹出去,再让下一个字符从栈顶继续匹配(即 j=nxt[st[top]])。因为之前已经将中间的匹配成功的串弹了出去,所以该算法正确。

代码
点击查看代码
  1. const ll N=2e6+10,inf=1ll<<40;
  2. ll n,m,nxt[N],f[N],st[N],top,ans;char s[N],t[N];
  3. namespace SOLVE{
  4. char buf[1<<20],*p1,*p2;
  5. #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
  6. inline ll rnt(){
  7. ll x=0,w=1;char c=gc();
  8. while(!isdigit(c)){if(c=='-')w=-1;c=gc();}
  9. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=gc();
  10. return x*w;
  11. }
  12. inline void PreNxt(){
  13. ll j=0;
  14. _for(i,2,m){
  15. while(j&&t[j+1]!=t[i])j=nxt[j];
  16. if(t[j+1]==t[i])++j;
  17. nxt[i]=j;
  18. }
  19. return;
  20. }
  21. inline void Machting(){
  22. ll j=0;
  23. _for(i,1,n){
  24. st[++top]=i;
  25. while(j&&t[j+1]!=s[i])j=nxt[j];
  26. if(t[j+1]==s[i])++j;
  27. f[i]=j;
  28. if(j==m){
  29. top-=m;
  30. j=f[st[top]];
  31. }
  32. }
  33. return;
  34. }
  35. inline void Print(){
  36. _for(i,1,top)putchar(s[st[i]]);
  37. return;
  38. }
  39. inline void In(){
  40. scanf("%s%s",s+1,t+1);
  41. n=strlen(s+1),m=strlen(t+1);
  42. PreNxt(),Machting();
  43. Print(),puts("");
  44. return;
  45. }
  46. }

[POI2006] OKR-Periods of Words

思路

设一个字符串 \(s\) 长度为 \(L\),最长周期长度为 \(l\),可以发现 \(s_{l+1}\sim s_{L}\) 是第二遍循环的 \(Q\) 的前缀,也就是说 \(s_{l+1}\sim s_{L}\) 是 \(S\) 的一个 \(\text{Border}\)

可以发现这个 \(\text{Border}\) 的长度越小 \(l\) 越大,那么我们就把问题转化成了 求一个字符串所有前缀的最小 \(\text{Border}\) 长度之和

设 \(mn_i\) 表示 \(Pre_i\) 的最小 \(\text{Border}\) 长度,分情况讨论它的值:

  • 如果 \(Pre_i\) 只有一个 \(\text{Border}\),那么 \(mn_i=nxt_i\)。
  • 否则由于 \(\text{Border}\) 的 \(\text{Border}\) 是 \(\text{Border}\),直接令 \(mn_i=mn_{nxt_i}\)。
代码

注意:如果输入时要数字和字符串混输就不要用 fread,否则会把字符串读到缓冲区里,导致无法读入字符串。(也可以单独再写一个读入字符串的函数,但是太麻烦没必要)

点击查看代码
  1. const ll N=1e6+10,inf=1ll<<40;
  2. ll n,nxt[N],mn[N],ans;char s[N];
  3. namespace SOLVE{
  4. inline ll rnt(){
  5. ll x=0,w=1;char c=getchar();
  6. while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
  7. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
  8. return x*w;
  9. }
  10. inline void PreNxt(){
  11. ll j=0;
  12. _for(i,2,n){
  13. while(j&&s[i]!=s[j+1])j=nxt[j];
  14. if(s[i]==s[j+1])++j;
  15. nxt[i]=j;
  16. if(j&&!nxt[j])mn[i]=j;
  17. else mn[i]=mn[j];
  18. if(mn[i])ans+=i-mn[i];
  19. }
  20. }
  21. inline void In(){
  22. n=rnt();
  23. scanf("%s",s+1);
  24. PreNxt();
  25. printf("%lld\n",ans);
  26. return;
  27. }
  28. }

字符串的匹配

思路

显然无法直接比较,因为需要求出在区间中的排名。

考虑用树状数组维护排名,但是直接维护任意区间的排名是很困难的。

再次像之前一样研究 \(\text{Border}\) 的性质。在求 \(nxt\) 数组时可以发现 每次失配时 \(\text{Border}\) 左端点都会减小,匹配成功后右端点加一,即这个区间是个整体不断右移的区间,且完全不会左移

那么每次失配就可以把左边的数暴力去除贡献,总复杂度 \(\Theta(n)\)。

会算排名后就可以直接匹配了。

时间复杂度 \(\Theta(n\log_2n)\)。

代码
点击查看代码
  1. const ll N=5e5+10,inf=1ll<<40;
  2. ll n,m,s,rk1[N],rk2[N],nxt[N],a[N],b[N];vector<ll>ans;
  3. class BIT{
  4. public:
  5. ll b[N]={0};
  6. inline void Update(ll x,ll y){while(x>0&&x<=s)b[x]+=y,x+=(x&-x);return;}
  7. inline ll Query(ll x){ll sum=0;while(x>0)sum+=b[x],x-=(x&-x);return sum;}
  8. inline void Clear(){memset(b,0,sizeof(b));}
  9. }r,bit;
  10. namespace SOLVE{
  11. char buf[1<<20],*p1,*p2;
  12. #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
  13. inline ll rnt(){
  14. ll x=0,w=1;char c=gc();
  15. while(!isdigit(c)){if(c=='-')w=-1;c=gc();}
  16. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=gc();
  17. return x*w;
  18. }
  19. inline bool Check(ll a,ll b){
  20. return bit.Query(a-1)==rk1[b]&&bit.Query(a)==rk2[b];
  21. }
  22. inline void PreRank(){
  23. _for(i,1,m){
  24. r.Update(b[i],1);
  25. rk1[i]=r.Query(b[i]-1);
  26. rk2[i]=r.Query(b[i]);
  27. }
  28. return;
  29. }
  30. inline void PreNxt(){
  31. nxt[1]=0;
  32. ll j=0;
  33. _for(i,2,m){
  34. bit.Update(b[i],1);
  35. while(j&&!Check(b[i],j+1)){
  36. _for(k,i-j,i-nxt[j]-1)bit.Update(b[k],-1);
  37. j=nxt[j];
  38. }
  39. if(Check(b[i],j+1))++j;
  40. nxt[i]=j;
  41. }
  42. return;
  43. }
  44. inline void Matching(){
  45. bit.Clear();
  46. ll j=0;
  47. _for(i,1,n){
  48. bit.Update(a[i],1);
  49. while(j&&!Check(a[i],j+1)){
  50. _for(k,i-j,i-nxt[j]-1)bit.Update(a[k],-1);
  51. j=nxt[j];
  52. }
  53. if(Check(a[i],j+1))++j;
  54. if(j==m){
  55. ans.push_back(i-j+1);
  56. _for(k,i-j+1,i-nxt[j])bit.Update(a[k],-1);
  57. j=nxt[j];
  58. }
  59. }
  60. return;
  61. }
  62. inline void In(){
  63. n=rnt(),m=rnt(),s=rnt();
  64. _for(i,1,n)a[i]=rnt();
  65. _for(i,1,m)b[i]=rnt();
  66. PreRank(),PreNxt(),Matching();
  67. printf("%ld\n",ans.size());
  68. far(i,ans)printf("%lld\n",i);
  69. return;
  70. }
  71. }

[HNOI2008]GT考试

思路

设 \(f_{i,j}\) 表示准考证的后 \(i\) 位中的前 \(j\) 位匹配上了 \(A\) 的方案数。答案显然为:

\[\sum_{i=1}^{m}f_{n,i}
\]

发现不好转移,那么我们再添加一个 \(g_{i,j}\) 表示匹配上前 \(i\) 位时通过加数字匹配上 \(j\) 位的方案数。

此时转移方程显然为:

\[f_{i,j}=\sum_{k=1}^{m}f_{i,k}\times g_{k,j}
\]

妈呀这不就是矩阵乘法吗?

妈呀这个 \(g\) 数组不是确定的吗?

妈呀这冲个矩阵快速幂不就行了吗?

所以现在我们只需要考虑如何求 \(g\) 数组即可。

其实这个也很简单,我们只需要枚举下一个数字,找到长度为 \(j\) 的前缀失配后跳到那个前缀上才能匹配成功即可。

时间复杂度 \(\Theta(m^3\log_2n)\)。

代码

点击查看代码
  1. const ll N=30,inf=1ll<<40;
  2. ll n,m,P,nxt[N],ans;char s[N];
  3. class Matrix{
  4. public:
  5. ll a[N][N];
  6. inline ll* operator[](ll x){return a[x];}
  7. inline void Clear(){memset(a,0,sizeof(a));}
  8. inline void One(){_for(i,1,m)a[i][i]=1;}
  9. inline Matrix operator*(Matrix mat){
  10. Matrix ans;ans.Clear();
  11. _for(i,1,m)_for(j,1,m)_for(k,1,m)
  12. ans[i][j]=(ans[i][j]+a[i][k]*mat[k][j]%P)%P;
  13. return ans;
  14. }
  15. }f,g;
  16. namespace SOLVE{
  17. inline ll rnt(){
  18. ll x=0,w=1;char c=getchar();
  19. while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
  20. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
  21. return x*w;
  22. }
  23. inline void GetNxt(){
  24. ll j=0;
  25. _for(i,2,m){
  26. while(j&&s[j+1]!=s[i])j=nxt[j];
  27. if(s[j+1]==s[i])++j;
  28. nxt[i]=j;
  29. }
  30. return;
  31. }
  32. inline void Pre(){
  33. _for(i,0,m-1){
  34. _for(j,'0','9'){
  35. ll k=i;
  36. while(k&&s[k+1]!=j)k=nxt[k];
  37. if(s[k+1]==j)++k;
  38. ++g[i+1][k+1],g[i+1][k+1]%=P;
  39. }
  40. }
  41. return;
  42. }
  43. inline Matrix fpow(Matrix a,ll b){
  44. Matrix ans;ans.Clear(),ans.One();
  45. while(b){
  46. if(b&1)ans=ans*a;
  47. a=a*a,b>>=1;
  48. }
  49. return ans;
  50. }
  51. inline ll GetAnswer(){
  52. f.Clear(),f[1][1]=1;
  53. g=fpow(g,n),f=f*g;
  54. _for(i,1,m)ans=(ans+f[1][i])%P;
  55. return ans;
  56. }
  57. inline void In(){
  58. n=rnt(),m=rnt(),P=rnt();
  59. scanf("%s",s+1);
  60. GetNxt(),Pre();
  61. printf("%lld\n",GetAnswer());
  62. return;
  63. }
  64. }

Trie

Phone List

思路

模板题。

代码
点击查看代码
  1. const ll N=1e6+10,inf=1ll<<40;
  2. ll T,n,ans;char s[N];
  3. class Trie{
  4. private:
  5. ll tot=1;
  6. class TRIE{public:ll nx[20];bool en=0;}tr[N];
  7. public:
  8. inline void Clear(){tot=1,memset(tr,0,sizeof(tr));return;}
  9. inline bool Add(char *s){
  10. ll len=strlen(s+1),q1=0,q2=0;
  11. ll p=1;
  12. _for(i,1,len){
  13. ll c=s[i]-'0';
  14. if(!tr[p].nx[c])q1=1,tr[p].nx[c]=++tot;
  15. p=tr[p].nx[c],q2|=tr[p].en;
  16. }
  17. tr[p].en=1;
  18. return (!q1)||(q2);
  19. }
  20. }tr;
  21. namespace SOLVE{
  22. inline ll rnt(){
  23. ll x=0,w=1;char c=getchar();
  24. while(!isdigit(c)){if(c=='-')w=-1;c=getchar();}
  25. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar();
  26. return x*w;
  27. }
  28. inline void In(){
  29. n=rnt(),ans=0;
  30. _for(i,1,n){
  31. scanf("%s",s+1);
  32. ans|=tr.Add(s);
  33. }
  34. puts(ans?"NO":"YES");
  35. tr.Clear();
  36. return;
  37. }
  38. }

The XOR Largest Pair

思路

01-Trie 模板题。

首先我们把所有数化成二进制数,从高位到低位塞进 01-Trie 里。

然后计算答案,有如下两种方式:

  • 法一(较为通用):

    对于每个数,我们都在根据它的二进制数在 01-Trie 上尽量反着走,异或出的结果一定是包含它的所有数对中最大的,再取个 \(\max\) 就能得到答案了。

    时间复杂度是稳的 \(\Theta(n\log_2n)\)。

  • 法二(自创,不通用):递归实现。传入两个节点,然后分情况讨论:

    • 两个节点都没有儿子了:直接退出并返回当前算出的答案。
    • 两个节点都只有一个儿子且都指向 \(0\) 或都指向 \(1\):都往这个儿子处走,且这一二进制位答案为 \(0\)。
    • 两个节点中有一个有儿子指向 \(1\),另一个节点有一个有儿子指向 \(0\):往这两个儿子处走,且这一二进制位答案为 \(1\)。

    最后返回的答案就是了(见代码)。

    可以发现最坏情况下要跑满整棵树,但很多情况下跑不满,而且树也填不到 \(\Theta(n\log_2n)\) 级别,所以时间复杂度严格小于 \(\Theta(n\log_2n)\),跑得飞快。

代码
点击查看代码
  1. const ll N=5e6+10,inf=1ll<<40;
  2. ll n,a[N];
  3. class Trie{
  4. public:
  5. ll tot=1;
  6. class TRIE{public:ll nx[2];}tr[N];
  7. inline void Add(ll num){
  8. stack<bool>st;
  9. _for(i,1,31)st.push(num&1),num>>=1;
  10. ll p=1;
  11. while(!st.empty()){
  12. if(!tr[p].nx[st.top()])tr[p].nx[st.top()]=++tot;
  13. p=tr[p].nx[st.top()];
  14. st.pop();
  15. }
  16. return;
  17. }
  18. inline ll Solve(ll lp,ll rp,ll num){
  19. ll ans=0;
  20. if(!tr[lp].nx[0]&&!tr[lp].nx[1]&&!tr[rp].nx[0]&&!tr[rp].nx[1])ans=num;
  21. if(!tr[lp].nx[0]&&tr[lp].nx[1]&&!tr[rp].nx[0]&&tr[rp].nx[1])ans=Solve(tr[lp].nx[1],tr[rp].nx[1],num<<1);
  22. if(tr[lp].nx[0]&&!tr[lp].nx[1]&&tr[rp].nx[0]&&!tr[rp].nx[1])ans=Solve(tr[lp].nx[0],tr[rp].nx[0],num<<1);
  23. if(tr[lp].nx[0]&&tr[rp].nx[1])ans=max(ans,Solve(tr[lp].nx[0],tr[rp].nx[1],num<<1|1));
  24. if(tr[lp].nx[1]&&tr[rp].nx[0])ans=max(ans,Solve(tr[lp].nx[1],tr[rp].nx[0],num<<1|1));
  25. return ans;
  26. }
  27. }tr;
  28. namespace SOLVE{
  29. char buf[1<<20],*p1,*p2;
  30. #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
  31. inline ll rnt(){
  32. ll x=0,w=1;char c=gc();
  33. while(!isdigit(c)){if(c=='-')w=-1;c=gc();}
  34. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=gc();
  35. return x*w;
  36. }
  37. inline void In(){
  38. n=rnt();
  39. _for(i,1,n)a[i]=rnt(),tr.Add(a[i]);
  40. printf("%lld\n",tr.Solve(1,1,0));
  41. return;
  42. }
  43. }

The XOR-longest Path

思路

异或和有一个重要性质:\(a\oplus b\oplus b=a\)。

那么两个节点 \(u\) 和 \(v\) 之间的路径异或和就相当于 \(u\) 到根的异或和异或上 \(v\) 到根的异或和(因为这两次中 \(u\) 和 \(v\) 的 \(\text{LCA}\) 到根的异或和会被异或没)。

于是问题就转化成了:找到两个节点 \(u\) 和 \(v\),使它们到根的异或和的异或和最大。

那么把每个点到根的异或和预处理出来,就变成上一道题了。

代码
点击查看代码
  1. const ll N=1e6+10,inf=1ll<<40;
  2. ll n,a[N],ans;vector<pair<ll,ll> >tu[N];
  3. class Trie {
  4. private:
  5. ll tot=1;
  6. class TRIE{public:ll nx[2];}tr[N];
  7. public:
  8. #define nx(p,i) tr[p].nx[i]
  9. inline void Add(ll num){
  10. ll p=1;stack<ll>st;
  11. _for(i,1,31)st.push(num&1),num>>=1;
  12. while(!st.empty()){
  13. if(!nx(p,st.top()))nx(p,st.top())=++tot;
  14. p=nx(p,st.top()),st.pop();
  15. }
  16. return;
  17. }
  18. ll Solve(ll lp,ll rp,ll num){
  19. ll ans=0;
  20. if(!nx(lp,0)&&!nx(lp,1)&&!nx(rp,0)&&!nx(rp,1))ans=num;
  21. if(nx(lp,0)&&!nx(lp,1)&&nx(rp,0)&&!nx(rp,1))ans=Solve(nx(lp,0),nx(rp,0),num<<1);
  22. if(!nx(lp,0)&&nx(lp,1)&&!nx(rp,0)&&nx(rp,1))ans=Solve(nx(lp,1),nx(rp,1),num<<1);
  23. if(nx(lp,0)&&nx(rp,1))ans=max(ans,Solve(nx(lp,0),nx(rp,1),num<<1|1));
  24. if(nx(lp,1)&&nx(rp,0))ans=max(ans,Solve(nx(lp,1),nx(rp,0),num<<1|1));
  25. return ans;
  26. }
  27. #undef nx
  28. }tr;
  29. namespace SOLVE {
  30. char buf[1<<20],*p1,*p2;
  31. #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
  32. inline ll rnt(){
  33. ll x=0,w=1;char c=gc();
  34. while(!isdigit(c)){if(c=='-')w=-1;c=gc();}
  35. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=gc();
  36. return x*w;
  37. }
  38. void Dfs(ll u,ll fa,ll num){
  39. tr.Add(num);
  40. far(pr,tu[u]){
  41. ll v=pr.first,w=pr.second;
  42. if(v==fa)continue;
  43. Dfs(v,u,num^w);
  44. }
  45. }
  46. inline void In(){
  47. n=rnt();
  48. _for(i,1,n-1){
  49. ll u=rnt(),v=rnt(),w=rnt();
  50. tu[u].push_back(make_pair(v,w));
  51. tu[v].push_back(make_pair(u,w));
  52. }
  53. Dfs(1,0,0);
  54. printf("%lld\n",tr.Solve(1,1,0));
  55. return;
  56. }
  57. }

Nikitosh 和异或

思路

刚开始由于我只会我的非主流最大异或对写法,想了好久都不会,后来看了眼题解学会了主流写法,瞬间就会做了……

设 \(l_i\) 表示 \(1\sim i\) 中异或和最大的一段区间,\(r_i\) 表示 \(i\sim n\) 中异或和最大的一段区间(注意:\(i\) 不必须是左/右端点)。

答案显然是:

\[ans=\max_{i=1}^{n-1}\{l_{i}+r_{i+1}\}
\]

那么如何求 \(l_i\) 和 \(r_i\) 呢?

一段区间的异或和一定是该区间左右端点各自的前缀/后缀异或和的异或和,那么我们预处理出所有前/后缀异或和,再对每一个左/右端点查找包含它自己的最大异或对即可。

代码
点击查看代码
  1. const ll N=4e5+10,M=N<<5,inf=1ll<<40;
  2. ll n,a[N],sum1[N],sum2[N],l[N],r[N],ans;
  3. class Trie{
  4. private:
  5. ll tot=1;
  6. class TRIE{public:ll nx[2];}tr[M];
  7. public:
  8. inline void Add(ll num){
  9. stack<ll>st;ll p=1;
  10. _for(i,1,31)st.push(num&1),num>>=1;
  11. while(!st.empty()){
  12. if(!tr[p].nx[st.top()])tr[p].nx[st.top()]=++tot;
  13. p=tr[p].nx[st.top()],st.pop();
  14. }
  15. return;
  16. }
  17. inline ll Find(ll num){
  18. stack<ll>st;ll p=1,ans=0;
  19. _for(i,1,31)st.push(num&1),num>>=1;
  20. while(!st.empty()){
  21. if(tr[p].nx[st.top()^1])p=tr[p].nx[st.top()^1],ans=ans<<1|(st.top()^1);
  22. else p=tr[p].nx[st.top()],ans=ans<<1|st.top();
  23. st.pop();
  24. }
  25. return ans;
  26. }
  27. }tr1,tr2;
  28. namespace SOLVE{
  29. char buf[1<<20],*p1,*p2;
  30. #define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
  31. inline ll rnt(){
  32. ll x=0,w=1;char c=gc();
  33. while(!isdigit(c)){if(c=='-')w=-1;c=gc();}
  34. while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=gc();
  35. return x*w;
  36. }
  37. inline void In(){
  38. n=rnt(),tr1.Add(0),tr2.Add(0);
  39. _for(i,1,n)a[i]=rnt();
  40. _for(i,1,n){
  41. sum1[i]=sum1[i-1]^a[i],tr1.Add(sum1[i]);
  42. l[i]=max(l[i-1],tr1.Find(sum1[i])^sum1[i]);
  43. }
  44. for_(i,n,1){
  45. sum2[i]=sum2[i+1]^a[i],tr2.Add(sum2[i]);
  46. r[i]=max(r[i+1],tr2.Find(sum2[i])^sum2[i]);
  47. }
  48. _for(i,1,n-1)ans=max(ans,l[i]+r[i+1]);
  49. printf("%lld\n",ans);
  50. return;
  51. }
  52. }
\[\Huge\mathfrak{The\ End}
\]

「学习笔记」字符串基础:Hash,KMP与Trie的更多相关文章

  1. 「学习笔记」Treap

    「学习笔记」Treap 前言 什么是 Treap ? 二叉搜索树 (Binary Search Tree/Binary Sort Tree/BST) 基础定义 查找元素 插入元素 删除元素 查找后继 ...

  2. 「学习笔记」Min25筛

    「学习笔记」Min25筛 前言 周指导今天模拟赛五分钟秒第一题,十分钟说第二题是 \(\text{Min25}​\) 筛板子题,要不是第三题出题人数据范围给错了,周指导十五分钟就 \(\text{AK ...

  3. 「学习笔记」FFT 之优化——NTT

    目录 「学习笔记」FFT 之优化--NTT 前言 引入 快速数论变换--NTT 一些引申问题及解决方法 三模数 NTT 拆系数 FFT (MTT) 「学习笔记」FFT 之优化--NTT 前言 \(NT ...

  4. 「学习笔记」FFT 快速傅里叶变换

    目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...

  5. 「学习笔记」wqs二分/dp凸优化

    [学习笔记]wqs二分/DP凸优化 从一个经典问题谈起: 有一个长度为 \(n\) 的序列 \(a\),要求找出恰好 \(k\) 个不相交的连续子序列,使得这 \(k\) 个序列的和最大 \(1 \l ...

  6. 「刷题笔记」哈希,kmp,trie

    Bovine Genomics 暴力 str hash+dp 设\(dp[i][j]\)为前\(i\)组匹配到第\(j\)位的方案数,则转移方程 \[dp[i][j+l]+=dp[i-1][j] \] ...

  7. 「学习笔记」ST表

    问题引入 先让我们看一个简单的问题,有N个元素,Q次操作,每次操作需要求出一段区间内的最大/小值. 这就是著名的RMQ问题. RMQ问题的解法有很多,如线段树.单调队列(某些情况下).ST表等.这里主 ...

  8. 「学习笔记」递推 & 递归

    引入 假设我们想计算 \(f(x) = x!\).除了简单的 for 循环,我们也可以使用递归. 递归是什么意思呢?我们可以把 \(f(x)\) 用 \(f(x - 1)\) 表示,即 \(f(x) ...

  9. 「学习笔记」Fast Fourier Transform

    前言 快速傅里叶变换(\(\text{Fast Fourier Transform,FFT}\) )是一种能在\(O(n \log n)\)的时间内完成多项式乘法的算法,在\(OI\)中的应用很多,是 ...

随机推荐

  1. Node.js精进(1)——模块化

    模块化是一种将软件功能抽离成独立.可交互的软件设计技术,能促进大型应用程序和系统的构建. Node.js内置了两种模块系统,分别是默认的CommonJS模块和浏览器所支持的ECMAScript模块. ...

  2. Java开发学习(五)----bean的生命周期

    一.什么是生命周期 首先理解下什么是生命周期? 从创建到消亡的完整过程,例如人从出生到死亡的整个过程就是一个生命周期. bean生命周期是什么? bean对象从创建到销毁的整体过程. bean生命周期 ...

  3. php 二维数组转换一维数组

    $result = array_reduce($res, function ($result, $value) { return array_merge($result, array_values($ ...

  4. iNeuOS工业互联网操作系统,增加搜索应用、多数据源绑定、视图背景设置颜色、多级别文件夹、组合及拆分图元

    目       录 1.      概述... 2 2.      搜索应用... 2 3.      多数据源绑定... 3 4.      视图背景设置颜色... 4 5.      多级别文件夹 ...

  5. python小题目练习(一)

    题目:输出1+2+3+4+5+--+100的总数,并打印出这行式子 代码展示:# 1.定义一个初识变量total,用于后面每次循环进行累加值 total = 0# 2.利用for循环遍历累加for i ...

  6. python基础教程:定义类创建实例

    类的定义 在Python中,类通过class关键字定义,类名以大写字母开头 >>>class Person(object): #所有的类都是从object类继承 pass #pass ...

  7. 编译调试Net6源码

    前言 编辑调试DotNet源码可按照官网教程操作,但因为网络问题中间会出现各种下载失败的问题,这里出个简单的教程(以6为版本) 下载源码 下载源码 GitHub下载源码速度极慢,可替换为国内仓库htt ...

  8. OWL页面创建Copy功能,把选择内容复制到QC

  9. 9.4 苹果macOS电脑如何安装Android开发环境(Android Studio)

    下载 来到官方下载界面(需要 科 学 上 网),下载最新版本,点击Download,然后同意协议,在点击下载:如果平常看文档,可以点击Google中国Android开发者官网(部分用户可能也需要科 学 ...

  10. 《Domain Agnostic Learning with Disentangled Representations》ICML 2019

    这篇文章是ICML 2019上一篇做域适应的文章,无监督域适应研究的问题是如何把源域上训练的模型结合无lable的目标域数据使得该模型在目标域上有良好的表现.之前的研究都有个假设,就是数据来自哪个域是 ...