CSP2019前夜きちんとしたテンプレートは、のために以下の方法を使用します
1.データ構造
1.1。仮想ツリー
説明:
ツリーを考えると\(K \)キーポイント、仮想ツリーを構築し、LCAのキーの任意の2点の唯一のキーポイントは保持されますと、木の本来の木と虚先祖関係の祖先関係は一貫しています。これは、仮想ツリーが最大であり証明することができます((2K-1)\ \ ) ポイント。
DFSによって順序ソートのすべてのキーポイントは、さえに少しエッジを追加する動的なスタックの保守を使用しています。時間計算(O(K \ N-ログ)\)\。
注:
(LCAすべての点)を処理しなければならないが、第一のスタックポイントに追加された
コード:
addedge0_(u,v)
示し\(U \)とV(\ \ )側との間に接続され、右側には実際の状況に応じて決定されます。
bool cmp(int x,int y) {return dfn[x]<dfn[y];}
void build()
{
sort(ky+1,ky+kyn+1,cmp); rt = ky[1]; for(int i=2; i<=kyn; i++) rt = LCA(rt,ky[i]).first;
tp = 1; stk[tp] = rt; n0++; kid[n0] = rt; isky[ky[1]] = true;
for(int i=(rt==ky[1]?2:1); i<=kyn; i++)
{
int u = ky[i],lca = LCA(stk[tp],u).first;
if(lca==stk[tp]) {tp++; stk[tp] = u; n0++; kid[n0] = u; isky[u] = true;}
else
{
while(tp>1 && dep[stk[tp-1]]>dep[lca]) {addedge0_(stk[tp],stk[tp-1]); tp--;}
addedge0_(stk[tp],lca); tp--;
if(stk[tp]!=lca)
{
tp++; stk[tp] = lca; n0++; kid[n0] = lca;
}
tp++; stk[tp] = u; n0++; kid[n0] = u; isky[u] = true;
}
}
while(tp>1)
{
addedge0_(stk[tp],stk[tp-1]);
tp--;
}
}
1.2左翼木
説明:
操作が最小値をとり、単一の操作の時間計算量が超えることができないと、一の実施態様では、支持挿入、削除、マージスタックなどスタック\(Oを(\ N-ログ)\) 。
コード:
int merge(int u,int v)
{
if(u==0||v==0) return u+v;
if(val[u]>val[v]) swap(u,v);
son[u][1] = merge(son[u][1],v);
if(dis[son[u][1]]>dis[son[u][0]]) {swap(son[u][1],son[u][0]);}
dis[u] = dis[son[u][1]]+1;
return u;
}
void insert(int u,int x)
{
siz++; val[siz] = x;
rtn[u] = merge(rtn[u],siz);
}
int popnode(int u)
{
return merge(son[u][0],son[u][1]);
}
1.3。KDツリー
ノー
2.文字列
2.1。接尾辞配列
説明:
メソッド接尾辞配列、時間の複雑さを乗じ\(O(N \ N-ログ)\) 。:
sa[i]
位\(I \)ビットサフィックス
rk[i]
:サフィックス\(I \)ランク
h[i]
:接尾辞\(I \ )その前身サフィックスをLCP。
height[i]
:最初の\(I \) LCPの目の前で自分の名前に接尾辞。
注:
(1)w1
されないでh[1]=1
、次に(\ \ 2)のサイクルを開始します。
コード:
namespace SA
{
int height[N+3],h[N+3],tmp[N+3],st[lgN+2][N+3],buc[N+3];
void buildSA()
{
int *x = rk,*y = tmp;
for(int i=1; i<=S; i++) buc[i] = 0;
for(int i=1; i<=n; i++) buc[x[i]=a[i]]++;
for(int i=1; i<=S; i++) buc[i] += buc[i-1];
for(int i=n; i>=1; i--) sa[buc[x[i]]--] = i;
int p = 0,s = S;
for(int j=1; p<n; j<<=1)
{
p = 0;
for(int i=n-j+1; i<=n; i++) y[++p] = i;
for(int i=1; i<=n; i++) {if(sa[i]>j) y[++p] = sa[i]-j;}
for(int i=1; i<=s; i++) buc[i] = 0;
for(int i=1; i<=n; i++) buc[x[y[i]]]++;
for(int i=1; i<=s; i++) buc[i] += buc[i-1];
for(int i=n; i>=1; i--) sa[buc[x[y[i]]]--] = y[i];
p = 1; swap(x,y); x[sa[1]] = 1;
for(int i=2; i<=n; i++) x[sa[i]] = y[sa[i]]==y[sa[i-1]]&&y[sa[i]+j]==y[sa[i-1]+j]?p:++p;
s = p;
}
for(int i=1; i<=n; i++) rk[sa[i]] = i;
for(int i=1; i<=n; i++) //w1
{
h[i] = h[i-1]==0?0:h[i-1]-1;
while(i+h[i]<=n && sa[rk[i]-1]+h[i]<=n && a[i+h[i]]==a[sa[rk[i]-1]+h[i]]) {h[i]++;}
}
for(int i=1; i<=n; i++) height[i] = h[sa[i]];
for(int i=1; i<=n; i++) st[0][i] = height[i];
for(int j=1; j<lgN; j++)
{
for(int i=1; i+(1<<j)-1<=n; i++) {st[j][i] = min(st[j-1][i],st[j-1][i+(1<<j-1)]);}
}
}
int querymin(int l,int r)
{
int g = lg2[r-l+1]; int *adr = st[g];
return min(adr[l],adr[r-(1<<g)+1]);
}
int LCP(int x,int y)
{
if(x==y) return n-x+1; if(rk[x]>rk[y]) swap(x,y);
return querymin(rk[x]+1,rk[y]);
}
}
using SA::buildSA;
using SA::LCP;
2.2拡張KMP
説明:
。A接尾辞文字列は、文字列全体に非常に類似LCP Manacherアルゴリズム、時間複雑さの各々に対して決定される\(O(N)\) 。
z[i]
接尾辞:\(I \)と文字列の長さLCP。
コード:
void Z_box()
{
int mx = 0,mxid = 0; z[1] = m;
for(int i=2; i<=m; i++)
{
if(a[i]!=a[1]) {z[i] = 0;}
else if(i>mx) {z[i] = 1;}
else {z[i] = min(z[i-mxid+1],mx-i+1);}
while(i+z[i]<=m && a[i+z[i]]==a[z[i]+1]) {z[i]++;}
if(i+z[i]-1>mx) {mx = i+z[i]-1; mxid = i;}
}
}
2.3。Manacherアルゴリズム
説明:
最長のサブストリングパリンドローム、時間複雑さの中心として各位置で取得\(O(N)\) 。
注:
(1)ない\(2N + 1 \)書かれた\(N- \) 。
コード:
void manacher()
{
for(int i=n; i>=1; i--) a[2*i] = a[i];
for(int i=1; i<=2*n+1; i+=2) a[i] = '#';
int mxid = 1,mx = 1; p[1] = 1;
for(int i=2; i<=2*n+1; i++)
{
if(i>mx) {p[i] = 1;}
else {p[i] = min(p[2*mxid-i],mx-i+1);}
while(i-p[i]>0 && i+p[i]<=2*n+1 && a[i+p[i]]==a[i-p[i]]) {p[i]++;}
if(i+p[i]-1>mx) {mx = i+p[i]-1; mxid = i;}
}
}
2.4サフィックスオートマトン
説明:
建設サフィックスオートマトンインクリメンタル方式、時間複雑\(O(N)\) 。
注一般サフィックスオートマトンBFSは、すべてのリーフノードの複雑さに別段の縮重、ノードの数であるトライ複雑に確保するために確立されなければなりません深さの合計。
注意事項:
(1)は、3つの変数を初期化することを忘れないでください。
コード:
void init()
{
siz = lstpos = rtn = 1;
}
void insertchar(char ch)
{
int p = lstpos,np; siz++; np = lstpos = siz; len[np] = len[p]+1; sz[np]++;
for(; p && son[p][ch]==0; p=fa[p]) {son[p][ch] = np;}
if(p==0) {fa[np] = rtn;}
else
{
int q = son[p][ch];
if(len[q]==len[p]+1) {fa[np] = q;}
else
{
siz++; int nq = siz; len[nq] = len[p]+1;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq] = fa[q]; fa[q] = fa[np] = nq;
for(; p && son[p][ch]==q; p=fa[p]) {son[p][ch] = nq;}
}
}
}
2.5。回文オートマトン
説明:
以上ではない文字列の異なる性質パリンドロームサブストリングの数が\(N- \)パリンドロームオートマトンノードはパリンドロームサブストリングを表します。
回文構成さオートマトンインクリメンタル方式、時間複雑\(O(N)\) 。
コード:
void initPAM()
{
siz = 1; fail[0] = fail[1] = 1; len[0] = 0; len[1] = -1; lstpos = 1;
}
void insertchar(int id)
{
int p = lstpos;
while(a[id-1-len[p]]!=a[id]) {p = fail[p];}
if(!son[p][a[id]])
{
siz++; int u = siz,v = fail[p];
while(a[id-1-len[v]]!=a[id]) {v = fail[v];}
fail[u] = son[v][a[id]]; len[u] = len[p]+2; son[p][a[id]] = u;
}
lstpos = son[p][a[id]];
}
2.6。リンドン分解
説明:
最小サイクル列は、それ自身で表されている場合、文字列はリンドン呼びます。
数リンドンに文字列の文字列は、リンドンは分裂と呼ばれます。リンドン分割方式のみ1つの文字列。
デュバルリンドン分割アルゴリズム、時間複雑\(O(N)\) 。
コード:
なし
2.7最小サイクル手段
ノー
3.グラフ理論
3.1。Tarjanのアルゴリズム
3.1.1。強連結成分
説明:
Tarjan要件が強く、図のコンポーネントを接続しています。DFSツリー、各点のDFSスタンプ統計(建設dfn[u]
)とそのアライバルタイムスタンプの最小点は、(low[u]
後者は、ポイント自体に対するものである場合)、強連結成分が最大に得ました。時間複雑\(O(N-M +)\) 。
注:
各トラバースの開始点から横断しないことに留意されたいです。
コード:
void tarjan(int u)
{
cnt++; dfn[u] = cnt; low[u] = cnt; ins[u] = true;
tp++; sta[tp] = u;
for(int i=fe0[u]; i; i=e0[i].nxt)
{
if(!dfn[e0[i].v]) {tarjan(e0[i].v); low[u] = min(low[u],low[e0[i].v]);}
else if(ins[e0[i].v]) low[u] = min(low[u],dfn[e0[i].v]);
}
if(low[u]==dfn[u])
{
num++; ca[num] = a[u];
while(sta[tp]!=u)
{
ins[sta[tp]] = false;
clr[sta[tp]] = num;
ca[num] += a[sta[tp]];
tp--;
}
ins[u] = false; clr[u] = num; tp--;
}
}
3.1.2ポイントビス通信コンポーネント
説明:
非二重ドット図の連結成分のためTarjanアルゴリズム。DFSツリーの構築low[u]
点場合木の側面を介して、この時点までに定義は、最小の非スタンプに達する\(Uが\)少なくとも1人の息子がある\(V \)を満足\(低[V] \ GE DFN [U]は\) 、次いで\(Uは\)カット点です。時間複雑\(O(N-M +)\) 。
ダブル-両面通信コンポーネントは、接続されたコンポーネントである必要があり、両端の切刃は、カットポイントでなければなりません。
コード:
(正方形の木の周りに構築されました)
namespace Graph
{
const int N = 1e6;
const int M = 4e6;
struct Edge
{
int v,nxt;
} e[(M<<1)+3];
int fe[N+3];
int fa[N+3];
int dfn[N+3],low[N+3],stk[N+3];
int n,en,cnt,tp;
void addedge(int u,int v)
{
en++; e[en].v = v;
e[en].nxt = fe[u]; fe[u] = en;
}
void Tarjan(int u)
{
cnt++; dfn[u] = low[u] = cnt;
tp++; stk[tp] = u;
for(int i=fe[u]; i; i=e[i].nxt)
{
int v = e[i].v;
if(v==fa[u]) continue;
if(!dfn[v])
{
fa[v] = u; Tarjan(v);
low[u] = min(low[u],low[v]);
if(low[v]>=dfn[u])
{
Tree::n++; Tree::addedge(u,Tree::n); Tree::addedge(Tree::n,u);
while(tp>0)
{
Tree::addedge(Tree::n,stk[tp]);
Tree::addedge(stk[tp],Tree::n);
tp--;
if(stk[tp+1]==v) {break;}
}
}
}
else {low[u] = min(low[u],dfn[v]);}
}
}
void buildTree()
{
Tree::n = n;
for(int i=1; i<=n; i++) Tree::a[i] = 1;
for(int i=1; i<=n; i++)
{
if(!dfn[i]) {Tarjan(i);}
}
}
}
3.1.3。BISは、コンポーネントのエッジを接続しました
説明:
ダブルポイント通信コンポーネント\(\ GE \)に\(\ GT \)ことができます。
コード:
少し。
3.2。オイラー
3.2.1。無向火Oulaループ
説明:
そこ図度への通信がなく、点の数が奇数である場合超えていない(2 \)\、オイラーの存在。DFS記録パスオイラー、時間複雑性を構築することができる後戻り\(O(M)\) 。
注:
(1)は、時間の複雑さのレベルの二乗にそうでない場合は、縮退、自殺トラバーサルを使用してください。
コード:
namespace Undirected
{
struct Edge{int v,nxt;} e[M+2];
int fe[N+2];
int dgr[N+2];
int ans[(N<<1)+2];
bool vis[M+2];
int n,m,en,tp;
void addedge(int u,int v)
{
en++; e[en].v = v;
e[en].nxt = fe[u]; fe[u] = en;
dgr[u]++;
}
void dfs(int u)
{
for(int i=fe[u]; i; i=fe[u])
{
fe[u] = e[i].nxt;
if(vis[(i+1)>>1]) continue;
vis[(i+1)>>1] = true;
dfs(e[i].v);
tp++; ans[tp] = (i&1) ? ((i+1)>>1) : -((i+1)>>1);
}
}
void solve()
{
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++){int x,y;scanf("%d%d",&x,&y);addedge(x,y);addedge(y,x);}
for(int i=1; i<=n; i++)
{
if(dgr[i]&1){printf("NO");return;}
}
tp = 0; dfs(e[1].v);
if(tp<m) {printf("NO"); return;}
printf("YES\n");
for(int i=tp; i>=1; i--)
{
printf("%d ",ans[i]);
}
}
}
3.2.2。ダイレクト火Oulaループ
説明:
度に等しいあり、貫通の点のオイラーの存在のために必要です。まだ記録パスバックのオイラー法によって構築することができます。時間複雑\(O(M)\) 。
コード:
namespace Directed
{
struct Edge{int v,nxt;} e[M+2];
int fe[N+2];
int ind[N+2];
int oud[N+2];
int ans[(N<<1)+2];
bool vis[M+2];
int n,m,en,tp;
void addedge(int u,int v)
{
en++; e[en].v = v;
e[en].nxt = fe[u]; fe[u] = en;
oud[u]++; ind[v]++;
}
void dfs(int u)
{
for(int i=fe[u]; i; i=fe[u])
{
fe[u] = e[i].nxt;
if(vis[i]) continue;
vis[i] = true;
dfs(e[i].v);
tp++; ans[tp] = i;
}
}
void solve()
{
scanf("%d%d",&n,&m); tp = 0;
for(int i=1; i<=m; i++)
{
int x,y; scanf("%d%d",&x,&y);
addedge(x,y);
}
for(int i=1; i<=n; i++)
{
if(ind[i]!=oud[i]) {printf("NO"); return;}
}
dfs(e[1].v);
if(tp<m) {printf("NO"); return;}
printf("YES\n");
for(int i=tp; i>=1; i--) printf("%d ",ans[i]);
}
}
4.数論
4.1。拡張ユークリッドアルゴリズム
説明:
ソリューション不定方程式\(= \ GCDによってAX + (B)\) の時間複雑。\(O(\ LOG(A + B))\) 。
コード:
llong exgcd(llong a,llong b,llong &x,llong &y)
{
if(b==0) {x = 1,y = 0; return x;}
llong nx,ny; llong ret = exgcd(b,a%b,nx,ny);
x = ny; y = nx-a/b*ny;
return ret;
}
4.2拡張中国の剰余定理
説明:
ソリューションの合同式は、プライムモジュラスないことがあります。これは、結合係数方程式にない互いに素行われます。
コード:
なし
4.3拡張BSGS
ノー
Cipollaアルゴリズム4.4。二次残基
ノー
5.多項式
ノー