Note_4.9
2019/4/9 奇奇怪怪的笔记
关于代码,基本上是现写的,可能连编译都过不了
因为是简单算法场,所以就很不走心了昂,/小纠结
图论相关
最小生成树
- prim,kruskal
- 最小生成树的切割性质
- 求次小生成树,动态mst
- 根据Kruskal的贪心性质,任意两个联通块之间的最小边一定在mst中
- 非mst一定可以通过一次换边得到权值更小的生成树。
最短路
dijkstra
priority_queue
是什么,我不知道
class dijkstra
{
const ll MN=262144,inf=0x3f3f3f3f
ll d[N];
struct node{ll x;int id;}t[MN<<1];
inline node Min(const node&x,const node&y){return x.x<y.x?x:y;}
inline void rw(int k,ll x){for(t[k+=MN].x=x;k>>=1;)t[k]=Min(t[k<<1],t[k<<1|1]);}\
inline void dij()
{
register int i,x;
for(i=1;i<MN<<1;++i) t[i].x=inf;//init
for(i=1;i<=n;++i) d[t[i+MN].id=i]=inf;//init
for(rw(S,d[S]=0);t[1]!=inf;)
{
rw(x=t[1].id,inf);
for(i=hr[x];i;i=e[i].nex)
if(d[e[i].to]>d[x]+e[i].w) rw(e[i].to,d[e[i].to]=d[x]+e[i].w);
}
}
}
spfa
SLF优化是什么,我不知道
int dis[MN];
bool in[MN];
std::queue<int> q;
bool spfa()
{
memset(dis,0x3f,sizeof dis);
memset(in,0,sizeof in);
reg int i,j;
in[T]=true;q.push(T);dis[T]=0;
while(!q.empty())
{
reg int u=q.front();q.pop();in[u]=false;
for(i=hr[u];i;i=e[i].nex)
if(dis[e[i].to]>dis[u]-e[i].c&&e[i^1].w)
{
dis[e[i].to]=dis[u]-e[i].c;
if(!in[e[i].to]) in[e[i].to]=true,q.push(e[i].to);
}
}
return dis[S]!=inf;
}
floyd
复杂度\(O(n^3)\),倍增floyd
图的联通
强联通分量
边/点双联通分量
割点、割边
圆方树
#define son e[i].to
int dfn[MN],low[MN],ind,st[MN],tp;
void tarjan(int x,int F=0)
{
dfn[x]=low[x]=++ind;st[tp++]=x;
reg int i;
for(i=hr[x];i;i=e[i].nex)if(i^F^1)//防止重边的影响
if(!dfn[son])
{
//如果两个点也算双联通分量的话,直接写(low[son]>=dfn[x])
tj(son,i);low[x]=min(low[x],low[son]);
if(dfn[x]==low[son])
{
++num;ins(x,num,Hr);
for(;st[tp]!=son;--tp) ins(num,st[tp-1],Hr);
}
else if(low[son]>dfn[x]) --tp,ins(x,son,Hr);
}
else low[x]=min(low[x],dfn[son]);
}
- 两个圆点在圆方树上的路径,与路径上经过的方点相邻的圆点的集合,就等于原图中两点简单路径上的点集
- 仙人掌上的最短路,圆方树上两个圆点之间权值不变,方点到子节点的边权等于父亲到它的最短路,求距离时,如果\(lca\)时方点,需要暴力转弯
网络流
dinic
\(O(EV^2)\),显然跑不满,对于二分图求匹配这样的特殊情况,复杂度是\(O(E\sqrt V)\)
//懒得写了,反正很熟练了哇
int d[MN],q[MN],top;
bool bfs()
{
memset(d,0,sizeof d);
reg int i,j;
for(d[q[i=top=1]=S]=1;i<=top;++i)
for(j=hr[q[i]];i;i=e[i].nex)
if(e[i].w&&!d[e[i].to])
d[q[++top]=e[i].to]=d[q[i]]+1;
}
int dfs(int x,int f)
{
if(x==T) return f;
int used=0;
for(int &i=cur[x];i;i=e[i].nex)
if(d[e[i].to]==d[x]+1&&e[i].w)
{
int tmp=dfs(e[i].to,min(f-used,e[i].w));
e[i].w-=tmp;e[i^1].w+=tmp;used+=tmp;
if(used==f) return used;
}
return d[x]=-1,used;
}
int dinic()
{
reg int maxflow=0;
while(bfs())
{
memcpy(cur,hr,sizeof cur);
maxflow+=dfs(S,inf);
}
return maxflow;
}
费用流spfa
\(O(\alpha VE)\),据说\(\alpha\)平均不超过\(2\)?
//假设每条边的费用是时间,那么每次增广相当于增大走路的时间
class Flow
{
bool vis[N],inq[N];ll dis[N];queue<int>q;
struct edge{int to,w,c,nex;}e[N*10];int cur[N],hr[N],en;
bool spfa()
{
for(int i=S;i<=T;++i) cur[i]=hr[i],inq[i]=0,dis[i]=1e18;
for(dis[S]=0,inq[S]=1,q.push(S);!q.empty();)
{
int u=q.front();q.pop(),inq[u]=0;
for(int i=hr[u];i;i=e[i].nex)
if(dis[e[i].to]>dis[u]+e[i].c&&e[i].w)
{
dis[e[i].to]=dis[u]+e[i].c;
if(!inq[e[i].to]) inq[e[i].to]=1,q.push(e[i].to);
}
}
return dis[T]<inf;
}
int dfs(int x,int f)
{
if(x==T||!f) return f;vis[x]=1;
int w,used=0;
for(int &i=cur[x];i;i=e[i].nex)
if(dis[e[i].to]==dis[x]+e[i].c&&e[i].w&&!vis[e[i].to])
{
w=dfs(e[i].to,min(f-used,e[i].w));
e[i].w-=w;e[i^1].w+=w;used+=w;
if(used==f) break;
}
vis[x]=0;return used;
}
public:
int S,T;
Flow(){S=0;en=1;}
void ins(int x,int y,int w,int c)
{ dbg1(x);dbg2(y);
e[++en]=(edge){y,w,c,hr[x]},hr[x]=en;
e[++en]=(edge){x,0,-c,hr[y]},hr[y]=en;
}
ll Ans(){ll r=0;while(spfa())r+=dfs(S,inf)*dis[T];return r;}
};
最小割树
//省去dinic部分
void clear(){for(reg int i=2;i<en;i+=2)e[i].w=e[i^1].w=(e[i].w+e[i^1].w)>>1;}
//因为是无向的图所有一开始正向反向边都有容量
void Build(int *id, int n)
{
if(n==1) return;
static int s[MN],t[MN];int cnts=0,cntt=0;
int cut=dinic(id[0],id[1]);
ins(id[0],id[1],cut,Hr,E,En);
for(reg int i=0;i<n;++i)
if(d[id[i]]) s[cnts++]=id[i];
else t[cntt++]=id[i];
memcpy(id,s,cnts<<2);
memcpy(id+cnts,t,cntt<<2);
if(cnts) Build(id,cnts);
if(cntt) Build(id+cnts,cntt);
}
带上下界的网络流?
树相关
树分治?
不会边分治的蒟蒻。。。
随便放几道题算了:1. 长链剖分 2. 点分治 3. 点分树
LCA?
树剖,倍增
RMQ
int pos[MN],fpos[MN<<2],st[MN<<2][20];//欧拉序,反欧拉序,st表
int dis(int x,int y)
{
if(pos[x]>pos[y]) std::swap(x,y);
reg int k=lg[pos[y]-pos[x]+1];
return fpos[min(st[pos[x]][k],st[pos[y]-(1<<k)+1][k])];
}
void dfs(int x,int fa=0)
{
pos[x]=++ind;fpos[ind]=x;st[ind][0]=ind;reg int i;
for(i=hr[x];i;i=e[i].nex)if(e[i].to^fa)
dfs(e[i].to,x),st[++ind][0]=pos[x];
}
inline void pre_work()
{
reg int i,j;dfs(1);
for(lg[0]=-1,i=1;i<(MN<<2);++i)lg[i]=lg[i>>1]+1;//注意是MN<<2
for(j=1;j<20;++j)for(i-1;i+(1<<j)-1<=ind&&i<=ind;++i)
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
动态树? ,prufer编码?, 跳转到前天的笔记
欧拉回路
(偷偷把原来写的东西搬上来。。。)
判定
有向图:所有点连通,且入度都等于出度
无向图:所有点连通,且度数为偶数
欧拉回路删掉一条边就是欧拉路径
欧拉路径:有且只有两个不符合欧拉回路要求的点,并且相连后就满足了算法
求欧拉路径就把不满足的点连起来,求欧拉回路后删掉这条边就好了
无向图
dfs,标记走过的边以及反边,走过就不走
每次回溯时记录边,这些边依次构成欧拉回路
有向图
边反向
和无向图差不多
然后可以当前弧优化
正确性
除了第一个点外,其它的点dfs是一定是入边出边成对出现
所以第一次加入答案的边一定是第一个点的入边
回溯时这个点又会多走一条出边,那么下次加入的一定是又是这个点的入边
归纳下去,一定会形成一条回路
namespace undir
{
inline void ins(int u,int v);//略略略
int de[MN],st[MN],top,mark[MN<<1];
void dfs(int u)
{
for(int &i=hr[u];i;i=e[i].nex)if(!mark[i])
{
mark[i]=mark[i^1]=1;int j=i;dfs(e[i].to);//反边反边!
st[++top]=j&1?((j-1)>>1):j>>1;
}
}
void solve();//包括读入,判定,略略略
}
namespace dir
{
inline void ins(int u,int v);//略略略
int ind[MN],outd[MN],st[MN],top,mark[MN];
void dfs(int u)
{
for(int &i=hr[u];i;i=e[i].nex)if(!mark[i])
{
mark[i]=1;int j=i;
dfs(e[i].to);st[++top]=j-1;
}
}
void solve();
}
匈牙利算法
KM算法是什么,我不知道
核心是找到一个起点和终点都在同一个集合的增广路径,路径上相邻的两条边中恰有一个在匹配中
int ans,bel[MN];
bool vis[MN];
bool find(int x)
{
for(int i=hr[x];i;i=e[i].nex)
if(!vis[e[i].to])
{
vis[e[i].to]=true;
if(bel[e[i].to]==-1||find(bel[e[i].to])) return bel[e[i].to]=x,1;
}
return 0;
}
void Main()
{
for(int i=0;i<n;++i)
{
memset(vis,0,sizeof vis);
if(find(i)) ++ans;
}
}
字符串什么的
KMP
找到最大的前缀等于后缀,\(=\)最大的\(border\)
int nex[MN];
void KMP(char *s,int len)
{
int i,k=0;
for(nex[1]=0,i=2;i<=len;++i)
{
while(k&&s[k+1]!=s[i])k=nex[k];
if(s[k+1]==s[i]) nex[i]=++k;
else nex[i]=0;
}
}
int March(char *s,int lens,char *t,int lent)
{
int i,k=0,tot;
for(i=1;i<=lent;++i)
{
while(k&&s[k+1]!=t[i]) k=nex[k];
if(s[k+1]==t[i]) ++k;
if(k==lens) ++tot,k=nex[k];//match!
}
return tot;
}
Z算法
和manacher类似的算法
\(z[i]\)表示\(s[i...]\)和原串的LCP
已知\(z[1...i-1]\),求\(z[i]\)
定义\(r[i]=i+z[i]\),即 \(i\) 后缀和原串的 LCP 在 \(i\) 后缀中的结束位置加\(1\)。
- 考虑 \(1\) 到 \(i-1\) 中 \(r[k]\) 最大的 \(k\),分成三种情况:
- \(i ≥ r[k]\):令 \(z[i] = 0\),往后暴力匹配,更新 \(k\) 为 \(i\)。
- \(i < r[k]\) 且 \(r[i-k+1] < z[k]\):\(z[i] = z[i-k+1]\)。
- \(i < r[k]\) 且 \(r[i-k+1] ≥ z[k]\):令 \(z[i] = r[k] - i\),往后暴力匹配,更新 \(k\) 为 \(i\)。
均摊 \(O(n)\)。
考虑做字符串匹配,令\(C=B+\)'#'\(+A\),求\(z\)数组,看哪些后缀的\(z\)值等于\(len_B\)
void Z()
{
z[1]=0;r[1]=1;
int i,k=1;
for(i=2;i<=N;++i)
{
if(r[k]<=i) z[i]=1;
else z[i]=min(r[k]-i,z[i-z[k]+1]);
while(i+z[i]<=N&&s[1+z[i]]==s[i+z[i]]) ++z[i];
r[i]=i+z[i];
if(r[i]>=r[k]) k=i;
}
}
AC自动机
class AC_automaton{
private:
int tr[N][26],cnt,e[N],fail[N];
public:
void insert(char *s)
{
int p=0;
for(int i=0;s[i];i++)
{
int k=s[i]-'a';
if(!tr[p][k])tr[p][k]=++cnt;
p=tr[p][k];
}
e[p]++;
}
void build()
{
queue<int>q;
memset(fail,0,sizeof(fail));
for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
while(!q.empty())
{
int k=q.front();q.pop();
for(int i=0;i<26;i++)
if(tr[k][i])
fail[tr[k][i]]=tr[fail[k]][i],q.push(tr[k][i]);
else tr[k][i]=tr[fail[k]][i];
}
}
int query(char *t)
{
int p=0,res=0;
for(int i=0;t[i];i++)
{
p=tr[p][t[i]-'a'];
for(int j=p;j&&~e[j];j=fail[j])res+=e[j],e[j]=-1;
}
return res;
}
}ac;
SA
为什么我的\(SA\)这么长啊啊啊
大佬的博客,供哪天突然不会的我学习
求\(sa\),基数排序
\(height[i]=LCP(sa[i],sa[i-1])\)
\(h[i]=height[rk[i]]\)
\(h[i]\ge h[i-1]-1\)
class SA
{
private:
int height[MN][19],p,q,sa[2][MN],rk[2][MN],num[MN];
public:
SA(){M(height);M(sa);M(rk);M(num);}
inline void init(){M(height);M(sa);M(rk);M(num);}
inline void build_sa()
{
register int i,j,k,mx;
for(i=1;i<=n;++i) num[s[i]-'a'+1]++;
for(i=1;i<=26;++i) num[i]+=num[i-1];
for(i=1;i<=n;++i) sa[1][num[s[i]-'a'+1]--]=i;
for(i=1;i<=n;++i) rk[1][sa[1][i]]=rk[1][sa[1][i-1]]+(s[sa[1][i-1]]!=s[sa[1][i]]);
mx=rk[1][sa[1][n]];
for(p=1,q=0,k=1;k<=n;k<<=1,p^=1,q^=1)
{
if(mx==n) break;
for(i=1;i<=n;++i) num[rk[p][sa[p][i]]]=i;
for(i=n;i;--i) if(sa[p][i]>k) sa[q][num[rk[p][sa[p][i]-k]]--]=sa[p][i]-k;
for(i=n-k+1;i<=n;++i) sa[q][num[rk[p][i]]--]=i;
for(i=1;i<=n;++i)
rk[q][sa[q][i]]=rk[q][sa[q][i-1]]+(rk[p][sa[q][i]]!=rk[p][sa[q][i-1]]||rk[p][sa[q][i]+k]!=rk[p][sa[q][i-1]+k]);
mx=rk[q][sa[q][n]];
}
for(i=k=1;i<=n;++i)
{
if(rk[p][i]==1) continue;if(k) k--;
for(j=sa[p][rk[p][i]-1];j+k<=n&&i+k<=n&&s[i+k]==s[j+k];++k);
height[rk[p][i]][0]=k;
}
for(i=1;i<=18;++i)for(j=n;j>=1&&j>(1<<i);--j)
height[j][i]=min(height[j][i-1],height[j-(1<<i-1)][i-1]);
}
inline int LCP(int x,int y)
{
x=rk[p][x];y=rk[p][y];
if(x>y) std::swap(x,y);
return min(height[y][lg[y-x]],height[x+(1<<lg[y-x])][lg[y-x]]);
}
}
SAM
核心?
- 作为一种可以表示所有后缀的状态的自动机,它得满足状态数尽可能的小
- SAM的做法:
- 每个状态表示所有\(Right\)集合相同的子串,这里\(Right\)集合的定义可是一个子串在原串中所有出现位置的右端点的集合。
- 对于每个状态,我们定义一个\(step\),表示该状态所能表示的所有子串中长度最大值
- \(fa\)指针,满足当前串的\(Right\)集合是\(fa\)指向的状态的真子集,且是最大的那一个,可以发现,\(fa\)指针所指向的状态一定是当前状态子串的一个后缀。
- 在线加点,每次加点后最多只会增加两个新的状态
class Suf_Automation
{
#define MX 2000005
private:
int c[MX][26],fa[MX],step[MX],v[MX],rk[MX],val[MX];
int last,cnt,n;
ll ans=0;
public:
inline void init(int len)
{
cnt=last=1;n=len;
for(int i=1;i<=n<<1;++i)
memset(c[i],0,sizeof c[i]),step[i]=fa[i]=v[i]=val[i]=0;
}
void Insert(int x)
{
int p=last,np=++cnt;step[np]=step[p]+1;val[np]=1;
for(;p&&!c[p][x];p=fa[p]) c[p][x]=np;
if(!p) fa[np]=1;
else
{
int q=c[p][x];
if(step[q]==step[p]+1) fa[np]=q;
else
{
int nq=++cnt;step[nq]=step[p]+1;
memcpy(c[nq],c[q],sizeof c[q]);
fa[nq]=fa[q];fa[np]=fa[q]=nq;
for(;c[p][x]==q;p=fa[p]) c[p][x]=nq;
}
}
last=np;
}
inline void Query()
{
register int i;
for(i=1;i<=cnt;++i) ++v[step[i]];
for(i=1;i<=n;++i) v[i]+=v[i-1];
for(i=1;i<=cnt;++i) rk[v[step[i]]--]=i;
for(i=cnt;i;--i)
{
val[fa[rk[i]]]+=val[rk[i]];
if(val[rk[i]]>1) ans=max(ans,1ll*val[rk[i]]*step[rk[i]]);
}
val[1]=0;
printf("%lld\n",ans);
}
#undef MX
}pac;
后缀树
咕咕咕
回文树
又称作“回文自动机”
每个节点都是一个回文串,然后\(fail\)指针维护的是它的最大的后缀回文串(同时也是前缀的)。
回文树的用处?
——by \(PinkRabbit\)
- 统计每个本质不同回文串出现次数的。这个 Manacher 很难做到(需要配合后缀自动机),但是回文自动机可以解决。
- 有趣的是,回文自动机可以支持前端插入呢,只需要再维护一个指向最长回文前缀的指针 \(head\)就好啦。因为回文是两边对称的呢,所以前端插入也没关系的。注意两个指针要同时更新哦。就是当前字符插完后,整个串形成回文串,那么就要更新\(head\)为当前这一端的\(fail\)
例题:APIO2014 回文串
//APIO 2014 回文串
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
}
#define MN 300005
int fail[MN],len[MN],cnt[MN];
int c[MN][26],en;ll ans;
char s[MN];
int main()
{
scanf("%s",s+1);
fail[0]=1;len[++en]=-1;
for(int i=1,j=1;s[i];++i)
{
while(s[i]!=s[i-len[j]-1]) j=fail[j];
if(!c[j][s[i]-'a'])
{
len[++en]=len[j]+2;
int k=fail[j];
while(s[i]!=s[i-len[k]-1])
k=fail[k];
fail[en]=c[k][s[i]-'a'];
c[j][s[i]-'a']=en;
}
j=c[j][s[i]-'a'];
++cnt[j];
}
for(int i=en;i>1;i--)
{
ans=max(ans,1ll*cnt[i]*len[i]);
cnt[fail[i]]+=cnt[i];
}
printf("%lld\n",ans);
return 0;
}
manacher
\(abcba\rightarrow a!b!c!b!a\)
记\(p[i]\)表示以\(i\)为回文中心的左右能延伸的最长长度\(+1\)
\(p[1]=1\)
\(r[i]=i+p[i]\)
考虑 \(1\) 到 \(i-1\) 中 \(r[k]\) 最大的 \(k\),分成三种情况:
- \(i \geq r[k]\),令\(p[i]=1\),然后暴力更新,更新 \(k\) 为 \(i\)。
- \(i < r[k]\)&&\(p[2*k-i]<r[k]-i\),\(p[i]=p[2*k-i]\)
- \(i<r[k]\)&&\(p[2*k-i]\geq r[k]-i\),令\(p[i]=r[k]-i\),然后暴力更新,更新 \(k\) 为 \(i\)。
for(k=p[1]=1,i=r[1]=2;i<=m;++i)
{
if(r[k]<=i) p[i]=1;
else p[i]=min(p[k*2-i],r[k]-i);
for(;s[i+p[i]]==s[i-p[i]];++p[i]);
r[i]=p[i]+i;r[i]>r[k]?k=i:0;p[i]-1>ans?ans=p[i]-1:0;
}
后缀平衡树?咕咕咕
Blog来自PaperCloud,未经允许,请勿转载,TKS!
Note_4.9的更多相关文章
- note_4.10
单位根反演 \[ \frac{1}{k}\sum_{i=0}^{k-1}\omega_k^{in}=[k|n] \] 所以 \[ \begin{equation} \begin{split} \sum ...
- Note_4.7
2019/4/7 奇奇怪怪的笔记 狄利克雷卷积 \(μ∗1=ϵ\),莫比乌斯反演 \(Id=φ∗1⇒φ=μ∗Id\) \(d=1∗1⇒1=μ∗d\) \(σ=Id∗1⇒Id=μ∗σ\) \(σ=φ∗ ...
- Note_4.1
2019/4/1 奇奇怪怪的笔记 多项式除法 问题描述 给定\(n\)次多项式\(A(x)\)和\(m\)次多项式\(B(x)\) 求: \[ A(x)=B(x)*C(x)+R(x) \] 我们要求\ ...
- boost::xml——基本操作以及中文乱码解决方案
下面是本人使用boost库的xml部分的基础操作,并且解决对于大家使用boost库读写中文xml内容出现的乱码问题. 1.实现boost库xml基本操作2.解决boost对xml中中文乱码问题3.实现 ...
- Spring MVC(3)Spring MVC 高级应用
一.Spring MVC 的数据转换和格式化 前面的应用,都只是用HandlerAdapter去执行处理器. 处理器和控制器不是一个概念,处理器是在控制器功能的基础上加上了一层包装,有了这层包装,在H ...
- Spring MVC(2)Spring MVC 组件开发
一.控制器接收各类请求参数 代码测试环境: 接收各类参数的控制器--ParamsController package com.ssm.chapter15.controller; @Controller ...
随机推荐
- UGUI image
1.创建UI->image 如何将图片在导入到场景中:将图片资源导入unity后,找到要导入的图片,将Texture Type改为“Sprite(2D and UI)”精灵模式,然后点击Appl ...
- gitignore文件不生效
git rm -r --cached . git add . git commit -m 'update .gitignore'
- 【转】如何使用jupyter编写数学公式(译)
[1.如何使用jupyter编写数学公式(译)][1] [1]: https://www.jianshu.com/p/93ccc63e5a1b
- Django异常 - ImportError: No module named django.core.management
Django错误 - ImportError: No module named django.core.management问题描述:在命令行输入 manage.py runserver,提示找不到d ...
- 2013.6.28 - KDD最后一天
今天收到中秋的邮件.KDD结果出来了,Zhongqiu Wang & Jingwen Huang 15th/561.
- AspNetCore架构图
asp,net,core All-in-One App All-in-One applications N-Layer 典型的应用层 分层项目骨架 Clean Architecture Layers ...
- java加密算法-SHA1
public class SHAUtil { /*** * SHA加密 生成40位SHA码 * @param 待加密字符串 * @return 返回40位SHA码 */ public static S ...
- node-images 进行图片压缩
前置条件:先安装images npm install images 编写代码 思路: 从指定文件夹遍历图片,执行压缩,压缩完成后放到指定文件夹中,并保持图片名无变化. var images = req ...
- python抽象基类
抽象基类 抽象基类提了一种方式,用以组织对象的层次结构,做出关于所需方法的断言,以及实现其他一些功能 要定义抽象基类,需要使用abc模块,该模块定义了一个元类(ABCMeta) 和一组装饰器(@abs ...
- 使用BERT模型生成token级向量
本文默认读者有一定的Transformer基础,如果没有,请先稍作学习Transormer以及BERT. 相信网上有很多方法可以生成BERT向量,最有代表性的一个就是bert as service,用 ...