【初步完工】复习笔记
〇、前言
怎么说呢,算法啊题目啊什么的写完也怕自己会忘记,果然OI还是要记笔记的吧毕竟蒟蒻记性不好。。。【我会慢慢养成记笔记的习惯的Orz
起始于2015.4.14
初步完工于2015.5.5
一、入门
1.1 模拟
1.2 暴力
1.3 贪心
1.4 高精度
1.5 排序
快排的实现我竟然忘了?!【补充】
二、搜索
2.1 DFS深搜
2.2 BFS广搜
2.3 A*启发式搜索
2.4 IDDFS迭代加深深搜
2.5 DBFS双向广搜
2.6 IDA*迭代启发
三、计算几何
3.1 基础几何操作
#define pi acos(-1)
#define eps 1e-10
struct P{double x, y;}; //点的定义
struct line{ll a, b, c;} l[maxn]; //线的定义
P Rotate(P a, double rad){return (P){a.x*cos(rad)-a.y*sin(rad), a.x*sin(rad)+a.y*cos(rad)};} //逆时针旋转向量(弧度)
P operator + (P a, P b){return (P){a.x+b.x, a.y+b.y};} //向量加法
P operator - (P a, P b){return (P){a.x-b.x, a.y-b.y};} //向量减法
double operator * (P a, P b){return a.x*b.y-a.y*b.x;} //叉积,a*b>0表示向量b在向量a的左边
double dis(P a, P b){return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));} //两点间的距离
atan2(b.y-a.y, b.x-a.x) //向量求极角
P inter(line a, line b) //两向量求交点
{
double k1=(b.b-a.a)*(a.b-a.a), k2=(a.b-a.a)*(b.a-a.a), t=k2/(k1+k2);
return (P){b.a.x+t*(b.b.x-b.a.x), b.a.y+t*(b.b.y-b.a.y)};
}
3.2 半平面交
sort(l+1, l+m+1); //极角排序
cnt=0;
rep(i, 1, m)
{
if (l[i].ang!=a[cnt].ang) cnt++;
a[cnt]=l[i];
} //极角排重
L=1, R=0; q[++R]=a[1]; q[++R]=a[2];
rep(i, 3, cnt)
{
while (L<R && jud(q[R-1], q[R], a[i])) R--;
while (L<R && jud(q[L+1], q[L], a[i])) L++;
q[++R]=a[i];
}
while (L<R && jud(q[R-1], q[R], q[L])) R--;
while (L<R && jud(q[L+1], q[L], q[R])) L++;
3.3 凸包
第一种:坐标排序法
sort(p+1, p+1+tot, cmp); //按横坐标和纵坐标从小到大排序
v[++top]=p[1]; //v为凸包栈
rep(i, 2, tot) //正着做一次
{
while (top>1 && (v[top]-v[top-1])*(p[i]-v[top-1])<-eps) top--;
v[++top]=p[i];
}
int tmp=top;
down(i, tot-1, 1) //反着再做一次
{
while (top>tmp && (v[top]-v[top-1])*(p[i]-v[top-1])<-eps) top--;
v[++top]=p[i];
}
第二种:极角排序法
int k=1;
rep(i, 2, n) if (p[i].y<p[k].y || (p[i].y==p[k].y && p[i].x<p[k].x)) k=i;
swap(p[1], p[k]);
//先把横纵坐标最小的点移至第一
sort(p+2, p+n+1, cmp);
//所有点顺时针排列,相同角度时按距离从小到大排
s[++top]=p[1]; s[++top]=p[2];
rep(i, 3, n)
{
while (top>1 && (p[i]-s[top-1])*(s[top]-s[top-1])<=0) top--;
s[++top]=p[i];
}
动态加点维护凸包:
//点按照横纵坐标排序
set<P>::iterator r=q.lower_bound(p), l=r, t; l--;
if ((*r-*l)*(p-*l)<0) return;
now-=dis(*l, *r);
while (1)
{
t=r++;
if (r==q.end()) break;
if ((*r-p)*(*t-p)>0) break;
now-=dis(*t, *r);
q.erase(t);
}
while (l!=q.begin())
{
t=l--;
if ((*t-p)*(*l-p)>0) break;
now-=dis(*t, *l);
q.erase(t);
}
q.insert(p); l=r=q.find(p); l--; r++;
3.4 旋转卡壳
3.5 最大子矩阵
算法一(枚举障碍法):【补充】
算法二(悬线扫描法):【补充】
扩展:最大权值子矩阵、最大方形子矩阵
通过算法一或算法二求得所有极大子矩阵,可知最大权值子矩阵和最大方形子矩阵皆位于极大子矩阵中
四、图论
4.1 最短路
4.1.1 Dijstra(堆优化)
使用数据结构:STL-priority_queue,配对堆
4.1.2 SPFA(SLF和LLL优化)
SLF优化:若当期要加入到队列的点的 Dist 小于队首的 Dist,则将点加入到队首。(需使用STL-deque)
!q.empty()&&d[q.front()]>d[y] ? q.push_front(y) : q.push_back(y);
LLL优化:若队首的 Dist 大于队列中的平均 Dist,则将队首移到队末。
判断负环:若某点入队超过n次,则图中存在负环。
4.1.3 差分约束
Point1.Dist初始化:
如果将源点到各点的距离初始化为0,最终求出的最短路满足 它们之间相互最接近了;
如果将源点到各点的距离初始化为INF(无穷大),其中之一·为 0,最终求出的最短路满足 它们与该点之间相互差值最大。
Point2.若求最小方案则跑最长路,否则跑最短路。(先确定是最长路还是最短路,然后建图)。
4.2 树
4.2.1 Purfer Sequence
推荐博文:http://www.cnblogs.com/zhj5chengfeng/archive/2013/08/23/3278557.html
由树构建 Purfer Sequence 的过程:总共 n-2 步,每一步都在当前的树中寻找具有最小标号的叶子节点(度数为 1),将与其相连的点的标号设为 Purfer Sequence 的第 i 个元素,并将此叶子节点从树中删除,直到最后得到一个长度为 n-2 的 Purfer Sequence 和一个只有两个节点的树。
由 Purfer Sequence 构建树的过程:
1.先将所有编号为 1 到 n 的点的度赋初值为 1,然后加上它在 Purfer Sequence 中出现的次数,得到每个点的度;
2.先执行 n-2 步,每一步,选取具有最小标号的度为 1 的点 u 与 Purfer Sequence 中的第 i 个数 v 表示的顶点相连,得到树中的一条边,并将 u 和 v 的度减 1;
3.最后再把剩下的两个度为 1 的点连边,加入到树中。
4.2.2 DFS序列
构成:每个点按照DFS遍历依次加入到DFS序列
用处:以每个点为根的子树所包含的点在DFS序列中排成一列,所以DFS可以用来将子树表示成一维线性状态
扩展:通过子树互相加减,可得到任意两点路径所经过的点集
4.2.3 欧拉序列
构成:每个点按照DFS遍历依次加入到欧拉序列;与DFS序列不同的是,欧拉序列中的每个点在遍历其任意一棵子树后仍会再一次出现在欧拉序列中
用处:可通过转成RMQ问题求两点LCA
扩展:RMQ问题可转成RMQ±1问题
4.2.4 生成树
生成树个数:Matrix-Tree定理(Kirchhoff矩阵)
建图:Vii=点i度数 Vij=点i与点j是否有相连?-1:0
生成树个数=Kirchhoff矩阵的任一个n-1阶子行列式的值
扩展:存在必选边的话,缩边为点,然后对新图求值
4.2.5 MST(最小生成树)
Prim算法:【代码】
可用堆优化,适用于稠密图
Kruskal算法:【代码】
可用并查集优化,适用于稀疏图
MST性质:
- 任意一棵MST中相同权值的边的数量相等
- 每颗MST中除去权值大于x的边,剩余的图的连通性依旧相等
- 如果除去图中权值大于x的边,MST被分割成k个联通分支,图也被分成k个联通分支
- 生成树再加上一条边就会形成一个环
- 若一颗生成树不是MST,则增加某条边后再去除环中权值最大的边,可得到权值和更小的生成树
最小K度限制生成树MKST:【代码】
在满足点v的度数为K时,求MST。
算法流程:
- 删除点v,求余图中M个联通分量的MST
- 加入点v,求图的最小M度限制生成树
- 不断增加v的度数至K,每次加入一条与点v相连的边,从环中删除一条权值最大且不与点v相连的边,多种方案选最优(此步可用动态规划优化加速)
最小有向生成树:【补充】【代码】
次小生成树:【代码】
求权值第二小的生成树
算法流程:
- 求原图的MST
- 求任意两点之间路径的权值最大边(此步可用动态规划优化加速)
- 枚举边并依次加入,并删除环中的权值最大边(除新加的边之外)
4.2.6 划分树问题
将某棵含有n个节点的树划分成k个子树Pi,使得Min(W(Pi))最大
算法一流程(贪心+二分答案):【代码】
- 二分答案得到上界
- 按照深度从大到小扫描个点,若当前部分的权值和>=上界则隔开
- 判断子树个数
算法二流程(移动割):【代码】
- 选择任何一个度为1的结点作为根,将k-1个割都放在与其唯一相连的边上
- 计算当前划分方案的Min(W(Pi))
- 计算所有可能的移动割方案,求能使移动后的割所对应的子树的W(now)最大的方案
- 如果W(now)<=Min(W(Pi)),则进行此次移动,并转到第2步;否则退出算法
优化方法:先用算法一得到初始方案(下届设为权值和/(k-1)),再进行算法二
权函数W扩展:权函数需满足W(T)>=W(T'),其中树T'是树T的子树
问题扩展:求Max(W(Pi))最小【补充】,求Max(W(Pi))-Min(W(Pi))最小【补充】
4.2.7 LCA
离线Tarjan:
int Find(int x) //并查集找根
{
int a=x, b; while (h[a]!=a) a=h[a];
while (x!=a) b=h[x], h[x]=a, x=b; //路径压缩
return a;
}
void dfs(int x) {v[x]=1; travel(x) if (!v[p->y]) d[p->y]=d[x]+p->z, dfs(p->y); v[x]=++now;}
void tarjan(int x)
{
h[x]=x; travel(x) if (v[p->y]<v[x]) tarjan(p->y), h[p->y]=x;
while (q[now+1].a==x) now++, ans[q[now].n]-=2*d[Find(q[now].b)];
}
bool cmp(node x, node y){return v[x.a]<v[y.a];}
int main()
{
now=0; dfs(1);
rep(i, 1, m) q[i].a=read(), q[i].b=read(), q[i].n=i;
rep(i, 1, m) if (v[q[i].a]<v[q[i].b]) swap(q[i].a, q[i].b);
rep(i, 1, m) ans[i]=d[q[i].a]+d[q[i].b];
sort(q+1, q+1+m, cmp);
now=0; tarjan(1);
rep(i, 1, m) printf("%d\n", ans[i]);
}
在线欧拉序列转RMQ:【代码】
4.3 网络
4.3.1 二分图
4.3.1.1 匈牙利算法
主要描述:对于由 xi 和 yi 构成的二分图中,对于每个 xi 都查询一次增广路,查询的时候若 yi 找过一次就不再重复查找;k[i] 表示 yi 所匹配的 x 点。
bool Find(int x)
{
travel(x) if (!b[p->y])
{
b[p->y]=true;
if (!k[p->y] || Find(k[p->y])) { k[p->y]=x; return true; }
}
return false;
}
4.3.1.2 Hopcroft-Karp算法
主要描述:先通过DFS预处理出Dist标号,然后利用Dist标号实现同时查找多条最短增广路。
//DFS初始Dist标号
queue <int> q; int dis=MAX;
rep(i, 1, n) if (!choose[i]) q.push(i), d[i]=0; else d[i]=MAX;
while (!q.empty())
{
int x=q.front(); q.pop();
if (d[x]>dis) break;
travel(x) if (!k[p->y])
dis=d[x];
else if (d[k[p->y]]==MAX) d[k[p->y]]=d[x]+2, q.push(k[p->y]);
}
4.3.1.3 KM算法
初始化:初始化 lx[i] 为与 xi 所连的边的最大权值,ly[i] 都为0;vx 和 vy 数组为访问标记(每个点只访问一次);l[i] 为与 yi 所连的 x 点;st[i] 用来加速KM标记的修改。
主要描述:先找增广路,若找不到增广路的话则通过 st[i] 修改 lx 和 ly。
bool Find(int x)
{
vx[x]=1;
rep(y, 1, ny) if (!vy[y])
{
int a=lx[x]+ly[y]-v[x][y];
if (!a)
{
vy[y]=1;
if (!l[y] || Find(l[y])) { l[y]=x; return true; }
}
else st[y]=min(st[y], a);
}
return false;
}
int KM()
{
//初始化
rep(i, 1, nx) lx[i]=-MAX; clr(ly, 0); clr(l, 0);
rep(i, 1, nx) rep(j, 1, ny) if (lx[i]<v[i][j]) lx[i]=v[i][j];
//
rep(i, 1, nx)
{
rep(j, 1, ny) st[j]=MAX;
while (1)
{
clr(vx, 0); clr(vy, 0);
if (Find(i)) break;
//修改KM标号
int a=MAX; rep(j, 1, ny) if (!vy[j] && a>st[j]) a=st[j];
rep(j, 1, nx) if (vx[j]) lx[j]-=a;
rep(j, 1, ny) if (vy[j]) ly[j]+=a; else st[j]-=a;
//
}
}
int ans=0;
rep(i, 1, nx) if (l[i]) ans+=v[l[i]][i];
return ans;
}
4.3.1.4 二分图特性
最小路径覆盖=总点数-最大匹配数(DAG图+拆点)
最小覆盖点集=最大匹配数(DAG图)
最大权值环覆盖=最大完美匹配
最小改动最大匹配:所有边的权值乘上n,然后对于原匹配的边则加一,改图后求最大完美匹配。
4.3.2 网络流
4.3.2.1 最大流
sap+gap+当前弧优化
#define travel(x) for(edge *p=d[x]; p; p=p->n) if (p->z)
int h[maxv], gap[maxv], S, T, V; //gap优化
edge *d[maxv]; //当前弧优化
int n, m, ans;
int sap(int now, int flow)
{
if (now==T) return flow;
int rec=0, ret;
travel(now) if (h[p->y]+1==h[now])
{
ret=sap(p->y, min(flow-rec, p->z));
p->z-=ret, p->pair->z+=ret, d[now]=p;
if ((rec+=ret)==flow) return flow;
}
if (!(--gap[h[now]])) h[S]=V;
++gap[++h[now]]; d[now]=fir[now];
return rec;
}
inline int maxflow()
{
clr(h, 0), clr(gap, 0); gap[0]=V; rep(i, 1, V) d[i]=fir[i];
int flow=0; while (h[S]<V) flow+=sap(S, inf);
return flow;
}
4.3.2.2 最小费用最大流
SPFA版:多次查找最短路并增广。较适用于稠密图。
int S, T, V, d[maxv], minf[maxv], pre[maxv]; ll cost=0;
bool b[maxv];
edge *aug[maxv];
deque <int> q;
inline bool spfa()
{
rep(i, 1, V) d[i]=inf, b[i]=0; q.clear();
q.pb(S), d[S]=0, b[S]=1, minf[S]=inf, pre[S]=0;
while (!q.empty())
{
int x=q.front(), y; q.pop_front(); b[x]=0;
travel(x) if (d[y=p->y] > d[x]+p->c)
{
d[y]=d[x]+p->c; aug[y]=p; minf[y]=min(minf[x], p->f); pre[y]=x;
if (!b[y])
b[y]=1, (!q.empty() && d[q.front()]>d[y]) ? q.pf(y) : q.pb(y);
}
}
if (d[T]==inf) return 0;
cost+=ll(minf[T])*ll(d[T]);
for(int i=T; pre[i]; i=pre[i]) aug[i]->f-=minf[T], aug[i]->pair->f+=minf[T];
return 1;
}
inline ll flow() {while(spfa()); return cost;}
ZKW版:一次SPFA+KM标号思想。较适用于稀疏图。
int S, T, V, d[maxv]; ll cost=0;
int dist[maxv], st[maxv];
bool b[maxv];
deque <int> q;
inline void spfa() //标号d
{
rep(i, 1, V) d[i]=inf, b[i]=0; q.clear();
q.pb(S), d[S]=0, b[S]=1;
while (!q.empty())
{
int x=q.front(), y; q.pop_front(); b[x]=0;
travel(x) if (d[y=p->y] > d[x]+p->c)
{
d[y]=d[x]+p->c;
if (!b[y]) b[y]=1, (!q.empty() && d[q.front()]>d[y]) ? q.pf(y) : q.pb(y);
}
}
}
void dfs(int now) //标号dist
{
b[now]=1; int y;
travel(now)
if (d[now]+p->c==d[y=p->y] && !b[y])
dist[y]=dist[now]-p->c, dfs(y);
}
int aug(int now, int flow) //寻找增广路
{
if (now==T) {cost+=flow*(dist[S]-dist[T]); return flow;}
b[now]=1; int rec=0, y, ret;
travel(now) if (!b[y=p->y])
{
if (dist[now]==dist[y]+p->c)
{
ret=aug(y, min(flow-rec, p->f));
p->f-=ret, p->pair->f+=ret;
if ((rec+=ret)==flow) return flow;
}
else st[y]=min(st[y], dist[y]+p->c-dist[now]);
}
return rec;
}
inline bool relabel() //更改标号-KM标号思想
{
int a=inf;
rep(i, 1, V) if (!b[i]) a=min(a, st[i]);
if (a==inf) return 0;
rep(i, 1, V) if (b[i]) dist[i]+=a;
return 1;
}
inline void costflow()
{
spfa();
clr(b, 0); clr(dist, 0);
dfs(S);
while(1)
{
rep(i, 1, V) st[i]=inf;
while(1)
{
clr(b, 0);
if (!aug(S, inf)) break;
}
if (!relabel()) break;
}
}
4.3.2.3 上下界最大流
4.3.2.4 网络流模型
求图的连通度
求图的边连通度
4.4 图的性质
4.4.1 基本图性质
最大团数:两两互连的最大点集内的点数
最小染色:用最少的颜色染点,使得边两端的点的颜色不同
最大独立集:两两互不相连的最大点集内的点数
最小团覆盖:用最少的团覆盖所有点
连通度:删去某些点使得图不联通的最少点数
边连通度:删去某些边使得图不联通的最少边数
4.4.2 弦图
性质:最大团数=最小染色 最大独立集=最小团覆盖
4.4.2.1 MCS算法(最大势算法)【代码】
特点:可求完美消除序列
步骤:
- 选取未标号中label最大的点i,从大到小标号
- 与点i相连的未标号点的label+1
- 第1、2步循环n次
扩展:
求最小染色数:求出完美消除序列后,从后往前染上最小能染的颜色。
求最大独立集:求出完美消除序列后,从前往后选择点,能选就选。
4.5 图的联通
4.5.1 欧拉回路
中国邮路问题:要求全部边经过,每条边经过的次数不限
无向图:
- 求原图的奇点(度数为奇数)
- 以所有奇点为定点建图(得到的图是无向完全图)并求最小完备匹配
- 修改原图,求欧拉回路
有向图:
- 求原图所有入度不等于出度的顶点
- 以符合条件的所有顶点建有向图并求最小费用最大流
- 修改原图,求欧拉回路
4.5.2 2-Sat
缩环为点后反向建边
void tarjan(int x)
{
dfn[x]=low[x]=++now;
q[++top]=x, inq[x]=1;
travel(x)
if (!dfn[p->y]) tarjan(p->y), low[x]=min(low[x], low[p->y]);
else if (inq[p->y]) low[x]=min(low[x], dfn[p->y]);
if (low[x]==dfn[x])
{
scc++; int a=0;
while (a!=x) a=q[top--], inq[a]=0, b[a]=scc;
}
}
void dfs(int x){if (c[x]) return; else c[x]=-1; travel2(x) dfs(p->y);}
int main()
{
rep(i, 1, 2*n) if (!dfn[i]) tarjan(i);
rep(i, 1, n) if (b[i*2-1]==b[i*2]) {puts("NO"); return 0;} puts("YES");
rep(x, 1, 2*n) travel(x) if (b[x]!=b[p->y]) Add2(b[p->y], b[x]);
rep(i, 1, n) op[b[2*i-1]]=b[2*i], op[b[2*i]]=b[2*i-1];
rep(i, 1, scc) if(!v[i]) q[++top]=i;
while (top)
{
int now=q[top--];
if (c[now]) continue;
c[now]=1; dfs(op[now]);
travel2(now) if(--v[p->y]==0) q[++top]=p->y;
}
}
4.6 图的算法
4.6.1 Trajan
dfn表示第几个查找到,low表示遍历中最小dfn。
void tarjan(int x)
{
dfn[x]=low[x]=++now;
q[++top]=x, inq[x]=1;
travel(x)
if (!dfn[p->y]) tarjan(p->y), low[x]=min(low[x], low[p->y]);
else if (inq[p->y]) low[x]=min(low[x], dfn[p->y]);
if (low[x]==dfn[x])
{
scc++; int a=0;
while (a!=x) a=q[top--], inq[a]=0, b[a]=scc;
}
}
4.7 平面图
4.8 其他
4.8.1 最大最小比值最小路径【代码】
问题:求一条路径,使得路径上的最大边权值与最小边权值之比最小
解法:
- 将边按权值从大到小排序, s=1
- 从第s条边开始从小到大依次加入并查集,直到图联通
- t=s,并查集清空,从第t条边开始从大到小依次加入并查集,直到图联通
- ans更新,s=t+1,返回第二步
五、技巧与思想
5.1 莫队算法
5.1.1 分块莫队
主要特点:将所有区间询问按左端点从小到大的顺序分成√n块,然后每个块中的询问再按右端点从小到大排序。或者将左端点的范围[1,n]分成√n块,并判断每个询问的左端点所处于哪一块并扔入块内,再将每个块中的询问按右端点从小到大排序。
适用范围:可离线,可O(1)实现单点添加删除。
int main()
{
n=read(); m=read();
rep(i, 1, n) k[i]=read();
//对询问分块
int block = int(sqrt(n));
rep(i, 1, n) pos[i] = (i-1)/block+1;
rep(i, 1, pos[n]) bl[i] = block*(i-1)+1, br[i] = block*i; br[pos[n]] = n;
rep(i, 1, m) q[i].l=read(), q[i].r=read(), q[i].a=read(), q[i].b=read(), q[i].id=i;
sort(q+1, q+1+m, cmp);
//
int l = 1, r = 0;
rep(i, 1, m)
{
//左右移动上次询问区间,移动至本次询问的区间,并维护数据
while (l < q[i].l) del(k[l++]);
while (q[i].r < r) del(k[r--]);
while (q[i].l < l) add(k[--l]);
while (r < q[i].r) add(k[++r]);
//
ans[q[i].id] = Q(q[i].a, q[i].b);
}
rep(i, 1, m) printf("%d\n", ans[i]);
return 0;
}
5.1.2 树上莫队
六、字符串
6.1 KMP算法【代码】
6.2 扩展KMP算法
简述:求模式串和主串的每一个后缀的最长公共前缀
int k, p;
n[0] = l*2; n[1] = 0; while (s[n[1]] == s[n[1]+1]) n[1]++; p = n[k=1]+1;
rep(i, 2, l-1)
{
n[i] = i<p ? min(p-i, n[i-k]) : 0;
while (s[n[i]] == s[n[i]+i]) n[i]++;
if (i+n[i]>p) p = i+n[i], k = i;
}
6.3 Trie
6.4 后缀数组
//构建rank数组
rep(i, 1, n) rk[i]=s[i-1];
p=1; while (p<=n)
{
rep(i, 1, n) q[i].x=rk[i], q[i].y=i+p>n?0:rk[i+p], q[i].z=i;
sort(q+1, q+1+n);
a=0; rep(i, 1, n) if (q[i].x!=q[i-1].x || q[i].y!=q[i-1].y) rk[q[i].z]=++a; else rk[q[i].z]=a;
if (a==n) break; else p*=2;
}
//构建sa数组
rep(i, 1, n) sa[rk[i]]=i;
//构建h数组
a=0; rep(i, 1, n)
{
if (a) a--; p=sa[rk[i]-1];
while (s[p+a-1]==s[i+a-1]) a++; h[rk[i]]=a;
}
6.5 AC自动机
Trie树:父节点是子节点的前缀
Fail树:父节点是子节点的后缀
//建Trie
//建Fail边
q.push(0); f[0] = -1;
while (!q.empty())
{
x = q.front(); q.pop();
rep(i, 0, 25) if (r[x][i])
{
int fo = f[x];
while (fo >= 0 && !r[fo][i]) fo = f[fo];
if (fo < 0) f[r[x][i]] = 0; else f[r[x][i]] = r[fo][i];
q.push(r[x][i]);
}
}
//建FailTree
rep(i, 1, tc) e[i].y = i, e[i].z = fir[f[i]], fir[f[i]] = i; DFS(0);
6.6 后缀自动机
void Add(int c, int l)
{
int p = t, o = ++m;
d[o] = l;
while (p >= 0 && !r[p][c]) r[p][c] = o, p = f[p];
t = o;
if (p < 0) f[o] = 0;
else if (d[r[p][c]] == d[p] + 1) f[o] = r[p][c];
else {
int x = r[p][c], y = ++m;
rep(i, 0, 25) r[y][i] = r[x][i];
d[y] = d[p] + 1; f[y] = f[x]; f[x] = f[o] = y;
while (p >= 0 && r[p][c] == x) r[p][c] = y, p = f[p];
}
}
6.7 Manacher
rep(i, 0, l-1) s[i*2+1]='$', s[i*2+2]=c[i];
l=l*2+1; s[0]='#'; s[l]=s[l+1]='$';
ans=o=p=0;
rep(i, 1, l)
{
k[i] = i<p ? min(p-i, k[o*2-i]) : 1;
while (s[i+k[i]] == s[i-k[i]]) k[i]++;
if (i+k[i]>p) p=i+k[i], o=i;
ans = max(ans, k[i]-1);
}
6.8 有限状态自动机【代码】
七、数据结构
7.1 配对堆
其实就是个奇怪的双向链表(雾)?
删除节点就是将其子树上的所有数据拆乱然后两两重组。
ll d[maxn];
int roof=0;
int join(int v, int u)
{
if (d[v]<d[u]) swap(v, u);
h[v].l=u, h[v].r=h[u].ch, h[h[u].ch].l=v;
h[u].ch=v;
return u;
}
void push(int v){if (!roof) roof=v; else roof=join(roof, v);}
void update(int v)
{
if (v!=roof)
{
if (h[h[v].l].ch==v)
h[h[v].l].ch=h[v].r;
else
h[h[v].l].r=h[v].r;
if (h[v].r) h[h[v].r].l=h[v].l;
h[v].l=h[v].r=0;
roof=join(roof, v);
}
}
int st[maxn], top;
void pop()
{
if (!h[roof].ch) roof=0; else
{
top=0; int t=h[roof].ch;
while (t) if (h[t].r)
{
int k=h[h[t].r].r;
int v=h[t].r;
h[t].l=h[t].r=h[v].l=h[v].r=0;
st[++top]=join(t, v);
t=k;
}
else
{
st[++top]=t; h[t].l=h[t].r=0; break;
}
roof=st[top];
rep(i, 1, top-1) roof=join(roof, st[i]);
}
}
7.2 树套树
7.2.1 树状数组+权值线段树
树状数组可支持log修改,权值线段树有时得先离散化后在进行。对于建树我们可以稍微利用一下可持久化思想中的节约空间来判断【看代码】
struct node{node *l, *r; int sum;} *blank=new(node), *Tree[maxn], *r1[maxn], *r2[maxn]; //树节点结构体
void Init(){blank->l=blank->r=blank; blank->sum=0;} //初始化
void Build(int l, int r, node*&t) //新建一棵空树
{
if (t==blank) t=new(node), t->l=t->r=blank, t->sum=0;
if (l==r) return;
int mid=(l+r)>>1;
Build(l, mid, t->l), Build(mid+1, r, t->r);
}
void Add(int k, int y, int l, int r, node *u, node*&t)
{
if (t==blank) t=new(node), t->l=t->r=blank, t->sum=0;
t->sum=u->sum+y;
if (l==r) return; int mid=(l+r)>>1;
if (k<=mid)
t->r=u->r, Add(k, y, l, mid, u->l, t->l);
else
t->l=u->l, Add(k, y, mid+1, r, u->r, t->r);
}
inline void Change(int t, int k) //修改
{
node *p; int v=k(t); k(t)=k;
for(int x=t; x<=n; x+=lowbit(x))
Add(v, -1, 1, ln, t(x), p=blank), t(x)=p;
for(int x=t; x<=n; x+=lowbit(x))
Add(k, 1, 1, ln, t(x), p=blank), t(x)=p;
}
7.3 主席树(可持久化线段树)
其实说白了,主席树就是一个可持久化线段树。
void Add(int k, int l, int r, int u, int &t) //主席树的构建
{
if (!t) t=++z;
s(t)=s(u)+1;
if (l==r) return; int mid=(l+r)>>1;
if (k<=mid) r(t)=r(u), Add(k, l, mid, l(u), l(t));
else l(t)=l(u), Add(k, mid+1, r, r(u), r(t));
}
7.4 Splay
想看最复杂的具体实现的话可以移至BZOJ1500
inline void rotate(int x, int &k) //旋转
{
int y=h[x], z=h[y], l=(c[y][1]==x), r=l^1;
if (y==k) k=x; else c[z][c[z][1]==y]=x;
h[c[x][r]]=y, h[y]=x, h[x]=z;
c[y][l]=c[x][r]; c[x][r]=y;
update(y), update(x);
}
inline void Splay(int x, int &k){while (x!=k) rotate(x, k);} //Splay操作
int Find(int x, int k) //寻找第k大的值
{
pushdown(x);
int l=c[x][0], r=c[x][1];
if (size[l]+1==k) return x;
if (size[l]>=k) return Find(l, k);
return Find(r, k-size[l]-1);
}
void Del(int x) //删除
{
if (!x) return;
int l=c[x][0], r=c[x][1];
Del(l); Del(r); q.push(x);
h[x]=c[x][0]=c[x][1]=0;
tag[x]=rev[x]=0;
}
inline int Split(int k, int n) //分离区间
{
int x=Find(rt, k), y=Find(rt, k+n+1);
Splay(x, rt); Splay(y, c[x][1]);
return c[y][0];
}
void Build(int l, int r, int f) //建树
{
if (l>r) return;
int mid=(l+r)>>1, now=id[mid], last=id[f];
if (l==r)
{
sum[now]=a[l], size[now]=1, tag[now]=rev[now]=0;
if (a[l]>=0) ls[now]=rs[now]=ms[now]=a[l]; else ls[now]=rs[now]=0, ms[now]=a[l];
}
else Build(l, mid-1, mid), Build(mid+1, r, mid);
v[now]=a[mid], h[now]=last, update(now);
c[last][mid>f]=now;
}
inline void Insert(int k, int n) //添加
{
rep(i, 1, n) a[i]=read();
rep(i, 1, n) if (!q.empty()) id[i]=q.front(), q.pop(); else id[i]=++cnt;
Build(1, n, 0);
int x=Find(rt, k+1), y=Find(rt, k+2), z=id[(1+n)>>1];
Splay(x, rt); Splay(y, c[x][1]);
h[z]=y, c[y][0]=z;
update(y), update(x);
}
7.5 树链剖分
树链剖分需要维护八个数据域:Tree Size Belong Lower Head Where Deep cnt(树链数量)
#define k(x) Key[x]
#define t(x) Tree[x]
#define s(x) Size[x]
#define b(x) Belong[x]
#define low(x) Lower[x]
#define dep(x) Deep[x]
#define h(x) Head[x]
#define l(x) Left[x]
#define r(x) Right[x]
#define w(x) Where[x]
struct edge{int y; edge *n;} e[maxn*2], *fir[maxn], *pt=e;
inline void AddE(int x, int y)
{
pt->y=y, pt->n=fir[x], fir[x]=pt++;
pt->y=x, pt->n=fir[y], fir[y]=pt++;
}
int Tree[maxn], Size[maxn], Belong[maxn], Lower[maxn], Deep[maxn], Head[maxn], Where[maxn], cnt, Key[maxn];
//树链八个数据域:Tree Size Belong Lower Head Where Deep cnt(树链数量)
int Left[maxp], Right[maxp], sum[maxp], big[maxp], z, L, R;
//每个树链维护一颗线段树
int n, d[maxn], h[maxn], now;
void dfs(int x) //通过DFS将树转成链
{
int maxs=-inf, maxl=0;
travel(x) if (p->y!=h[x])
{
h[p->y]=x, d[p->y]=d[x]+1; dfs(p->y);
if (s(b(p->y))>maxs) maxs=s(b(p->y)), maxl=p->y;
}
if (maxl)
b(x)=b(maxl), w(x)=w(maxl)+1, s(b(x))++, dep(b(x))--;
else
cnt++, b(x)=cnt, w(x)=1, s(cnt)=1, low(cnt)=x, dep(cnt)=d[x];
travel(x) if (p->y!=h[x] && p->y!=maxl) h(b(p->y))=x;
}
void BuildT(int&k, int l, int r, int t) //对于每条树链建立一棵线段树
{
if (l==r){sum[t]=big[t]=k(k), k=h[k]; return;}
int mid=(l+r)>>1;
BuildT(k, l, mid, l(t)=++z), BuildT(k, mid+1, r, r(t)=++z);
sum[t]=sum[l(t)]+sum[r(t)], big[t]=max(big[l(t)], big[r(t)]);
}
inline void Build() //总建造过程
{
dfs(1); h(b(1))=0;
rep(i, 1, cnt) now=low(i), BuildT(now, 1, s(i), t(i)=++z);
}
void Edit(int k, int y, int l, int r, int t) //线段树修改
{
if (l==r){sum[t]=big[t]=y; return;}
int mid=(l+r)>>1;
if (k<=mid) Edit(k, y, l, mid, l(t)); else Edit(k, y, mid+1, r, r(t));
sum[t]=sum[l(t)]+sum[r(t)], big[t]=max(big[l(t)], big[r(t)]);
}
inline void Change(int x, int y){k(x)=y; Edit(w(x), y, 1, s(b(x)), t(b(x)));}
void QueryS(int l, int r, int t) //线段树询问
{
if (L<=l && r<=R) {now+=sum[t]; return;}
int mid=(l+r)>>1;
if (L<=mid) QueryS(l, mid, l(t));
if (mid<R) QueryS(mid+1, r, r(t));
}
inline void Qsum(int x, int y)
{
now=0; while (b(x)!=b(y))
{
if (dep(b(x))<dep(b(y))) swap(x, y);
L=w(x), R=s(b(x)), QueryS(1, s(b(x)), t(b(x))), x=h(b(x));
}
if (d[x]<d[y]) swap(x, y);
L=w(x), R=w(y), QueryS(1, s(b(x)), t(b(x)));
}
7.6 LinkCutTree
#define l(x) c[x][0]
#define r(x) c[x][1]
#define f(x) Father[x]
#define h(x) Head[x]
int n, c[maxn][2], Head[maxn], Father[maxn];
bool rev[maxn];
inline void pushdown(int x)
{
rev[x]^=1, rev[l(x)]^=1, rev[r(x)]^=1;
swap(l(l(x)), r(l(x))), swap(l(r(x)), r(r(x)));
}
inline void rotate(int x)
{
int y=f(x), z=f(y), l=(c[y][1]==x), r=l^1;
if (z) c[z][c[z][1]==y]=x;
f(c[x][r])=y, f(y)=x, f(x)=z;
c[y][l]=c[x][r]; c[x][r]=y;
}
inline void Splay(int x)
{
if (!x) return; if (rev[x]) pushdown(x); int y;
while (f(x))
{
if (rev[y=f(x)]) pushdown(y), pushdown(x);
if (!f(y)) h(x)=h(y), h(y)=0;
rotate(x);
}
}
inline void Acc(int x) //将点x至根的路径独立成一条链
{
int cmp=x, y; Splay(x);
f(r(x))=0, h(r(x))=x, r(x)=0;
while (h(x))
Splay(y=h(x)), f(r(y))=0, h(r(y))=y, r(y)=x, f(x)=y, h(x)=0, x=y;
Splay(cmp);
}
inline void Eve(int x){Acc(x); rev[x]^=1; swap(l(x), r(x));} //将x设为根
inline int Top(int x){Acc(x); int now=x; while (l(now)) now=l(now); return now;} //求根
inline void Build(){rep(i, 1, n) l(i)=r(i)=h(i)=f(i)=0;}
inline void Connect(int x, int y){Eve(x); Eve(y); Splay(y); h(x)=y;} //连边
inline void Destroy(int x, int y){Eve(x); Acc(y); f(x)=l(y)=0;} //删边
inline void Query(int x, int y){if (Top(x)==Top(y)) puts("Yes"); else puts("No");}
7.7 启发式合并
7.8 替罪羊树
int Left[maxn], Right[maxn], Key[maxn], Size[maxn], V=0, roof, dfn[maxn], top;
node *Tree[maxn];
void Build(int l, int r, int &t)
{
if (l>r) {t=0; return;}
int mid=(l+r)>>1;
t=dfn[mid], t(t)=blank, s(t)=r-l+1;
rep(i, l, r) Add(0, maxl, k(dfn[i]), t(t));
Build(l, mid-1, l(t)), Build(mid+1, r, r(t));
}
node *range[maxn];
int p[maxn], nr, np;
void ranging(int l, int r, int t) //BST查找区间
{
if (r<ls(t)) ranging(l, r, l(t)); //全在左边
else if (l>ls(t)) ranging(l-ls(t)-1, r-ls(t)-1, r(t)); //全在右边
else if (!l && r==s(t)-1) range[++nr]=t(t); //整棵树都是
else //分跨两边
{
p[++np]=k(t);
if (l<ls(t)) ranging(l, ls(t)-1, l(t));
if (r>ls(t)) ranging(0, r-ls(t)-1, r(t));
}
}
void getrange(int l, int r){nr=np=0; ranging(l-1, r-1, roof);}
void dfs(int t){if (l(t)) dfs(l(t)); dfn[++top]=t; if (r(t)) dfs(r(t));} //DFS序
void Recycle(node *t) //回收节点
{
if (!t->sum) return;
if (t->l) Recycle(t->l);
if (t->r) Recycle(t->r);
delete(t);
}
void reBuild(int &t)
{
top=0; dfs(t);
rep(i, 1, top) Recycle(t(dfn[i]));
Build(1, top, t);
}
八、数学相关(数论)
8.1 博弈论
推荐链接:http://www.cnblogs.com/NanoApe/p/4266216.html
8.1.1 博弈论性质
SG函数性质:
- SG值始终为非负整数。
- 每个状态的SG都与它能到达的状态的SG值不相同。
- 在满足上面两个条件的情况下SG值应该尽量小。
- SG=0的状态为必败态,SG≠0的状态为必胜态。
SG定理:对于一个组合游戏,它状态的SG值即为各个子游戏状态的SG值的异或和(Nim和)。
反胜利条件:上面的必胜和必败都是基于“无法操作者败”这个规则来解释的。若某组合游戏的胜利条件是“无法操作者胜”的话,必胜态必须满足:
- SG异或和>0且SG大于1的子游戏>0
- SG异或和=0且SG大于1的子游戏=0
其余的就是必败态了。
Nim阶梯:【补充】
8.1.2 常见的几种公平游戏:
Nim游戏:有N堆石子,每次可从一堆石子中拿走任意数量的石子。
解法:把每堆石子看成子游戏,其SG值等于其石子数。
图上移子游戏:一个有向图上有一个棋子沿着有向边移动。
解法:赤裸裸的SG定理直接用。
树上删边:N棵树,不断删边,每次删边也会把删完无法与根相连的子树删去。
解法:把树上的每个点看成一个组合游戏,它的儿子就是子游戏。
图上删边:上一题的升级版,这次图中可以出现环。
解法:先缩环,奇数边环缩成一条边,偶数边环缩成一个点。接着解法同上题。
NimK游戏:Nim的升级版,每次可从K堆拿走任意数量的石子。
解法:把N堆石子的石子数用二进制表示,统计每一个二进制位上的1的个数,若每一位上1的个数mod(k + 1)全为0,则必败,否则必胜。
Every-SG游戏:每个子游戏必须同时进行的组合游戏。
设D(T),当游戏T为先手必胜时D为最长回合数,当T为先手必败时D为最短回合数。
必胜条件当且仅当Dmax为奇数。
8.2 高斯消元法【代码】
8.3 行列式
8.4 FFT
属于只能照背死记的算法。
#include <complex>
#define cd complex <double>
#define PI acos(0.0)*2.0
#define ll long long
#define base 10000
cd a[maxn], b[maxn], c[maxn], A[maxn];
ll ans[maxn];
int n, len, n1[maxn], n2[maxn], s[maxn], ansn=0;
void fft(cd *a, bool flag)
{
rep(i, 0, n-1) s[i]=0;
for(int i=1, j=n; i<n; i*=2, j/=2) rep(h, j/2, j-1) s[h]+=i;
for(int i=1; i<n; i*=2) rep(j, 0, i-1) s[j+i]+=s[j];
rep(i, 0, n-1) A[i]=a[s[i]];
double pi=flag?PI:-PI;
for(int step=1; step<n; step*=2)
{
cd e=exp(cd(0, 2.0*pi/double(step*2))), w=cd(1, 0); //最难记的地方
for(int pos=0; pos<step; ++pos, w*=e)
for(int i=pos; i<n; i+=step*2)
{
cd ret=A[i], rec=w*A[i+step];
A[i]=ret+rec, A[i+step]=ret-rec;
}
}
if (!flag) rep(i, 0, n-1) A[i]/=n;
rep(i, 0, n-1) a[i]=A[i];
}
int main()
{
n=1; while (n<len*2) n*=2;
rep(i, 0, n-1) a[i]=cd(n1[i], 0); fft(a, true);
rep(i, 0, n-1) b[i]=cd(n2[i], 0); fft(b, true);
rep(i, 0, n-1) c[i]=a[i]*b[i]; fft(c, false);
rep(i, 0, n-1) ans[i]=(ll)(c[i].real()+0.5);
rep(i, 0, n-1) ans[i+1]+=ans[i]/base, ans[i]%=base;
ansn=n; while (ansn>0 && !ans[ansn]) ansn--;
}
8.5 欧拉函数
rep(i, 1, n) f[i]=i; rep(o, 2, n) if (f[o]==o) for(int i=o; i<=n; i+=o) f[i]=f[i]/o*(o-1);
九、规划
9.1 斜率优化
9.2 四边形不等式
9.3 矩阵快速幂优化决策转移
十、其他算法
十一、已达成成就
- 输出格式错误(NOIP2014)
- 文件名打错(STOI2015)
十二、注意事项
-
每个变量的大小和数组的范围留心
- 写完静态查错

浙公网安备 33010602011771号