note
约定 _xxx 表示有 xxx 功能的伪代码
笔记部分口胡 不保证完全正确
大部分都是数学
图论
生成树
稠密图上 \(\mathrm{Prim}\) 优 稀疏图上 \(\mathrm{Kruskal}\) 优
Kruskal
复杂度 \(O(n \log n)\)
struct Edge{int from,to,dis;}e[MAX<<1]; //这里存图比较特殊
int f[MAX];
//
void addedge(int from,int to,int dis) {e[++cnt]=(Edge){from,to,dis};} //特殊的加边
//并查集
void union(int x,int y);
int find(int x);
//
int Kruskal()
{
int re=0,tot=0;
for(int i=1;i<=MAX;i++) f[i]=i;
sort(e+1,e+num_edge+1,_cmpDis());
for(int i=1;i<=num_edge;i++)
{
int u=find(e[i].from),v=find(e[i].to);
if(u==v) continue;
tot+=1; re+=e[i].dis;
f[u]=v;
if(tot==n-1) break;
}
return re;
}
Kruskal 重构树
Prim
复杂度 \(O(n \log n)\)
#define pii pair<int,int>
#define mp make_pair
//
int head[MAX],vis[MAX],cnt;
struct Edge{int next,to,dis;}e[MAX<<1];
//
void addedge(int from,int to,int dis); //加边
//
int prim(int s)
{
int re=0;
priority_queue<pii,vector<pii>,_cmp<pii> > q; //_cmp=greater/less
q.push(mp(0,s));
while(!q.empty())
{
pii now=q.top(); q.pop();
int u=now.x,w=now.dis;
if(vis[u]) continue;
vis[u]=1;
ans+=w;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!vis[v]) q.push(e[i].dis,v);
}
}
return re;
}
重链剖分
建立复杂度 \(O(n)\)
int size[MAX],dep[MAX],fa[MAX],top[MAX],dfn[MAX],rnk[MAX],idx;
struct Edge{int next,to,dis;}e[MAX<<1];
_set(allVar,0);
//
void dfs1(int u,int f)
{
size[u]=1; dep[u]=dep[f]+1; fa[u]=f;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==f) continue;
dfs1(v,u);
size[u]+=size[v];
//如果这里是边权要下放到点权 即 init[v]=e[i].dis
if(size[son[u]]<size[v]) son[u]=v;
}
}
void dfs2(int u,int topf)
{
top[u]=topf; dfn[u]=++idx; rnk[idx]=u;
if(!son[u]) return ;
dfs2(son[u],topf);
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
对树的查询
对于一个节点 \(p\) 其与其子树为 _subtree(p)=_(dfn[p],dfn[p]+size[p]-1)
对于访问 \(u \sim v\) 有
int chain(int u,int v,...)
{
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
_modify(dfn[top[u]],dfn[u],...);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
_modify(dfn[u],dfn[v],...);
//如果这里是边权就是 _modify(dfn[u]+1,dfn[v],...)
return _need();
}
最近公共祖先(LCA)
尽管复杂度一样 但是树剖比倍增大概快4倍
树链剖分
单次查询复杂度 \(O(\log n)\)
跳链时每次跳深度大的点
int size[MAX],dep[MAX],fa[MAX],top[MAX],idx; //仅需要知道这些
struct Edge{int next,to,dis;}e[MAX<<1];
_set(allVar,0);
//
void addedge(int from,int to,int dis); //加边
//重链剖分
void dfs1(int u,int f);
void dfs2(int u,int topf);
//
int lca(int x,int y)
{
while(top[x]!=top[y])
{
if(dep[top[x]]>=dep[top[y]]) x=fa[top[x]];
else y=fa[top[y]];
}
return dep[x]<dep[y]?x:y;
}
倍增
建立复杂度 \(O(n \log n)\)
单次查询复杂度 \(O(\log n)\)
int log2[MAX],dep[MAX],fa[MAX][log2[MAX]];
struct Edge{int next,to,dis;}e[MAX<<1];
//
void addedge(int from,int to,int dis); //加边
//开始前运行
getLog2();
perDfs(root,root)
//
void getLog2()
{
log2[0]=-1;
for(int i=1;i<=MAX;i++) log2[i]=log2[i>>1]+1;
}
void preDfs(int u,int f)
{
dep[u]=dep[f]+1; fa[u][0]=f;
for(int i=1;(1<<i)<=dep[u];i++) fa[u][i]=fa[fa[u][i-1]][i-1];
for(int i=head[u];i;i=e[i].next)
if(e[i].to!=f) preDfs(e[i].to,u);
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) x=fa[x][log2[dep[x]-dep[y]]];
if(x==y) return x;
for(int i=log2[dep[x]];i>=0;i--)
if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
st
\(O(n\log n)\) 建表 \(O(1)\) 查询
dfn[] 存的是一个欧拉序
int dep[MAX],log2[MAX<<1],fa[N<<1][21],dfn[MAX<<1],q[MAX<<1],idx;
//
void addedge(int from,int to); //加边
//
void dfs(int u,int f)
{
dfn[u]=++idx; q[idx]=u; dep[u]=dep[f]+1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==f) continue;
dfs(v,u);
q[++idx]=u;
}
}
void build(int s)
{
dfs(s,s);
for(int i=1;i<=idx;i++) fa[i][0]=q[i];
for(int j=1;j<=20;j++)
for(int i=1;i+(1<<j)<=idx;i++)
{
int f1=fa[i][j-1],f2=fa[i+(1<<(j-1))][j-1];
fa[i][j]=dep[f1]<dep[f2]?f1:f2;
}
log2[0]=-1;
for(int i=1;i<=idx;i++) log2[i]=log2[i>>1]+1;
}
int lca(int u,int v)
{
if(dfn[u]>dfn[v]) swap(u,v);
u=dfn[u]; v=dfn[v];
int t=log2[v-u+1],f1=fa[u][t],f2=fa[v-(1<<t)+1][t];
return dep[f1]<dep[f2]?f1:f2;
}
最短路
Dijkstra
复杂度稳定的 \(O((v+e) \log v)\)
#define pii pair<int,int>
#define mp make_pair
//
int dis[MAX],vis[MAX];
struct Edge{int next,to,dis;}e[MAX<<1];
//
void addedge(int from,int to,int dis); //加边
//
void Dijkstra(int S,int T)
{
_set(dis,INF); _set(vis,0);
priority_queue<pii,vector<pii>,greater<pii> > q;
q.push(mp(0,S)); dis[S]=0;
while(!q.empty())
{
int u=q.top().second; q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].dis)
{
dis[v]=dis[u]+e[i].dis;
if(!vis[v]) q.push(mp(dis[v],v));
}
}
}
return dis[T];
}
SPFA
大部分复杂度 \(O(kn)\) 最坏 \(O(nm)\)
在分层图上跑的飞飞飞飞快
int dis[MAX],vis[MAX];
struct Edge{int next,to,dis;}e[MAX<<1];
//
void addedge(int from,int to,int dis); //加边
//
void SPFA(int S,int T)
{
_set(dis,INF); _set(vis,0);
queue<int> q;
q.push(S); dis[S]=0; vis[S]=1;
while(!q.empty())
{
int u=q.front(); q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].dis)
{
dis[v]=dis[u]+e[i].dis;
if(vis[v]==0) vis[v]=1,q.push(v);
}
}
}
}
Floyd
复杂度 \(O(n^3)\)
int e[MAX][MAX];
//
_set(e,INF);
_set(e[i][i],0);
_set(e[u][v],min(dis[u][v]));
void Floyd()
{
for(int k=1;k<=MAX;k++)
for(int i=1;i<=MAX;i++)
for(int j=1;j<=MAX;j++) e[i][j]=min(e[i][j],e[i][k]+e[k][j]);
}
Johnson
全源最短路 复杂度 \(O(nm \log m)\)
新建一个超级源点 \(S\) ,从其向所有点连一条边权为 \(0\) 的边
用队列优化的 \(Bellman-Ford\) 算法求出 \(0\) 号点到其它所有点的最短路
对于一条 \(u \to v\) 边权为 \(w\) 的边,将边权重新设为 \(w+h_u-h_v\)
再以每个点为起点 求出任意两点之间的最短路
int dis[MAX],vis[MAX],h[MAX];
struct Edge{int next,to,dis;}e[MAX<<1];
//
void addedge(int from,int to,int dis);
bool SPFA(int S); //普通的SPFA+判负环 true/false表是否有负环
void Dijkstra(int S);
//
void Johnson()
{
_getGap();
int S=_nonGapV(); //S=0
for(int i=1;i<=MAX;i++) addedge(S,i,0);
if(SPFA(S)) //SPFA跑出来的最短路记为 h[MAX]
{
_getNegativeCircle();
return ;
}
for(int u=1;u<=n;u++)
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
e[i].w+=h[u]-h[v];
}
for(int i=1;i<=n;i++) Dijkstra(i);为
}
拓扑排序
对于一个图 如果删除某个入度为 \(0\) 的点之后 可以原图拓扑排序
那么 新图同样可以
BFS
时间复杂度 \(O(E+V)\)
bool topoSort()
{
queue<int> q; q._push(inDeg[i]==0);
vector<int> ans;
while(!q.empty())
{
int u=q.front(); q.pop();
ans.push_back(u);
for(int i=head[u];i;i=e[i].next)
if((--inDeg[u])==0) q.push(v);
}
if(ans.size()==n) return _get();
else _haveCircle();
}
性质
- 序列中包含每个顶点,且每个顶点仅出现一次
- 若 \(V_A\) 在序列中排 \(V_B\) 前,则图中不存在 \(V_B\) 到 \(V_A\) 的路径
- 能拓扑排序的图 一定是有向无环图 反之亦然
Tarjan
缩点
有向图
int dfn[MAX],low[MAX],idx,st[MAX],top;
int col[MAX],clr,size[MAX],inDeg[Max];
//
void addedge(int from,int to);
//
void tarjan(int u)
{
low[u]=low[v]=++idx; st[++top]=u;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
else if(!col[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
col[u]=++clr;
while(st[top]!=u) col[st[top--]]=clr,size[clr]++;
top--; size[clr]++;
}
}
void rebuild()
{
memset(head,0,sizeof(head)); cnt=0;
for(int i=1;i<=EDGENUM;i++)
{
int u=col[edge[i].from],v=col[edge[i].to];
if(u==v) continue;
addedge(u,v); inDeg[v]++;
}
}
割点
无向图
int dfn[MAX],low[MAX],idx,count;
//
void addedge(int from,int to);
//
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
tarjan(v); low[u]=min(low[u],low[v]);
if(u!=root&&low[v]>=dfn[u]) cut[u]=1;
if(u==root) count++;
}
else low[u]=min(low[u],dfn[v]);
}
if(u==root&&count>1) cut[u]=1;
}
割边(桥)
int low[MAX],dfn[MAX],idx,count;
//
void addedge(int from,int to);
//
void tarjan(int u)
{
dfn[u]=low[u]=++idx;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
tarjan(v); low[u]=min(low[u],low[v]);
if(low[v]>dfn[u]) count++;//这条边就是桥
}
else low[u]=min(low[u],dfn[v]);
}
}
二分图最大匹配
尽管网络流也能做
匈牙利算法
复杂度 \(O(n^2)\)
int n,m,col[MAX];
bool e[MAX][MAX],vis[MAX][MAX];
//
void addedge(int u,int v) {e[u][v]=1;}
//
bool dfs(int u)
{
for(int i=1;i<=m;i++)
if(e[u][i]&&!vis[i])
{
vis[i]=1;
if(!col[i]||dfs(col[i]))
{
col[i]=u;
return true;
}
}
return false;
}
int match(int n,int m) //一部分为n 一部分为m
{
int re=0;
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
if(dfs(i)) re++;
}
return re;
}
网络流
复杂度 \(O(n \sqrt e)\)
最大流
尽管几乎都是 \(\mathrm{ISAP}\) 跑的比 \(\mathrm{Dinic}\) 快 \(\mathrm{ISAP}\) 的常数比较小
但本质上复杂度没有改变
最坏复杂度 \(O(n^2m)\)
Dinic
struct Edge{int next,from,to,flow;}e[N];
_set(head,-1);
//
void addcur(int from,int to,int cur)
{
e[cnt]=(Edge){head[from],from,to,cur}
head[from]=cnt++;
e[cnt]=(Edge){head[to],to,from,0};
head[to]=cnt++;
}
int dfs(int u,int flow,int t)
{
if(s==t) return flow;
if(!flow) return 0;
int re=flow;
for(int &i=cur[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(!e[i].flow||dis[u]+1!=dis[v]) continue;
int flowV=dfs(v,min(re,e[i].flow),t);
if(!flowV) dis[v]=-1; //delete v
re-=flowV;
e[i].flow-=flowV; e[i^1]+=flowV;
if(!re) break;
}
return flow-re;
}
bool bfs(int s,int t)
{
//这里没写
}
int dinic()
{
int re=0;
while(bfs(s,t)) re+=dfs(S,INF,T);
}
ISAP
int n,m,s,t,head[N],cnt,maxflow;
int cur[N],dep[N],pre[N],num[N];
struct edge{int next,to,dis;}e[M<<1];
_set(head,-1);
//加边时注意要 addedge(u,v,w); addedge(v,u,0);
void addedge(int from,int to,int dis) //加边
//
//ISAP(S,T)
void ISAP(int s,int t)
{
int u=s;
bfs(t);
for(int i=1;i<=n;i++)
num[dep[i]]++;
while(dep[s]<n)
{
if(u==t)
{
maxflow+=addflow(s,t);
u=s;
}
bool flag=0;
for(int &i=cur[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(dep[u]==dep[v]+1&&e[i].dis)
{
flag=1;
u=v;
pre[v]=i;
break;
}
}
if(!flag)
{
int mn=n-1;
for(int i=head[u];i!=-1;i=e[i].next)
if(e[i].dis)
mn=min(mn,dep[e[i].to]);
if(!(--num[dep[u]])) break;
num[dep[u]=mn+1]++;
cur[u]=head[u];
if(u!=s) u=e[pre[u]^1].to;
}
}
}
int addflow(int s,int t)
{
int ans=INF,u=t;
while(u!=s)
{
ans=min(ans,e[pre[u]].dis);
u=e[pre[u]^1].to;
}
u=t;
while(u!=s)
{
e[pre[u]].dis-=ans;
e[pre[u]^1].dis+=ans;
u=e[pre[u]^1].to;
}
return ans;
}
void bfs(int t)
{
queue<int> q;
for(int i=1;i<=n;i++) cur[i]=head[i];
for(int i=1;i<=n;i++) dep[i]=n;
dep[t]=0;
q.push(t);
while(!q.empty())
{
int u=q.front(); q.pop();
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(dep[v]==n&&e[i^1].dis)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
}
最小费用最大流
int vis[N],dis[N],pre[N],last[N],flow[N],maxflow,mincost;
int n,m,s,t,head[N],cnt=1;
struct edge{int next,to,cur,dis;}e[N<<1];
_set(head,-1);
//
void addcur(int u,int v,int cur,int dis)
{
addedge(u,v,cur,dis); addedge(v,u,0,-dis);
}
void addedge(int from,int to,int cur,int dis)
{
e[++cnt].next=head[from]; head[from]=cnt;
e[cnt].to=to; e[cnt].dis=dis; e[cnt].cur=cur;
}
//
bool spfa()
{
queue<int> q;
_set(dis,INF); _set(flow,INF); _set(vis,0);
q.push(s); vis[s]=1;
dis[s]=0; pre[t]=-1;
while(!q.empty())
{
int u=q.front(); q.pop();
vis[u]=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].to;
if(e[i].cur>0&&dis[v]>dis[u]+e[i].dis)
{
dis[v]=dis[u]+e[i].dis;
pre[v]=u;
last[v]=i;
flow[v]=min(flow[u],e[i].cur);
if(!vis[v]) vis[v]=1,q.push(v);
}
}
}
return pre[t]!=-1;
}
void MCMF()
{
while(spfa())
{
int now=t;
maxflow+=flow[t];
mincost+=flow[t]*dis[t];
while(now!=s)
{
e[last[now]].cur-=flow[t];
e[last[now]^1].cur+=flow[t];
now=pre[now];
}
}
}
2-SAT
给出 \(n\) 个布尔变量 \(x_1 \sim x_n\) 有 \(m\) 个条件
每个条件形如 \(x_i 为真/假或 x_j 为真/假\)
int n,m,head[N],cnt;
int dfn[N],col[N],low[N],vis[N],idx,clr;
struct edge{int next,to;}e[N<<1];
stack<int> s;
//
void tarjan(int u);
void add(int from,int to);
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x,a,y,b;
cin>>x>>a>>y>>b;
if(a&b)
{
add(x,y+n);
add(y,x+n);
}
if(!a&&b)
{
add(x+n,y+n);
add(y,x);
}
if(a&&!b)
{
add(x,y);
add(y+n,x+n);
}
if(!a&&!b)
{
add(x+n,y);
add(y+n,x);
}
}
for(int i=1;i<=n*2;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++)
if(col[i]==col[i+n])
{
puts("IMPOSSIBLE");
return 0;
}
puts("POSSIBLE");
for(int i=1;i<=n;i++)
printf("%d ",col[i]>col[i+n]?1:0);
return 0;
}
void add(int from,int to)
{
e[++cnt].next=head[from];
e[cnt].to=to;
head[from]=cnt;
}
void tarjan(int u)
{
low[u]=dfn[u]=++idx;
s.push(u);
vis[u]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u])
{
clr++;
while(!s.empty())
{
int now=s.top(); s.pop();
vis[now]=0;
col[now]=clr;
if(now==u) break;
}
}
}
树同构
给出 \(m\) 个 \(n\) 个节点的无根树
令某个点为根 则每个点有一个前驱 则树成为有根树
对于两个树 \(T_1\) 和 \(T_2\) 假设能将 \(T_1\) 的所有点重新标号使得 \(T_1\) 和 \(T_2\) 完全相同
则称其为同构的 或 相同的形态
将 \(m\) 个树分成若干个等价类
ll m,cnt,head[N],ans[N][N];
struct EDGE{int next,to;}e[N*N*3];
//
int main()
{
cin>>m;
for(int i=1;i<=m;i++)
{
cnt=0; memset(head,0,sizeof(head));
int n; cin>>n;
for(int j=1;j<=n;j++)
{
int x; cin>>x;
if(!x) continue;
addedge(j,x); addedge(x,j);
}
for(int j=1;j<=n;j++) ans[i][j]=h(j,0);
sort(ans[i]+1,ans[i]+n+1);
for(int j=1,k=0;j<=i;j++)
{
while(k<=n)
if(ans[i][++k]!=ans[j][k]) break;
if(k>n)
{
cout<<j<<endl;
break;
}
}
}
return 0;
}
void addedge(int from,int to)
{
e[++cnt]=EDGE{head[from],to};
head[from]=cnt;
}
ll h(int x,int f)
{
ll s[N],re=N,top=0;
for(int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if(v!=f) s[++top]=h(v,x);
}
sort(s+1,s+top+1);
for(int i=1;i<=top;i++) re=re*NUM+s[i];
return re*NUM+N+1;
}
斯坦纳树
设 \(f(i,S)\) 表以 \(i\) 为根的一棵树,包含 \(S\) 的最小边权和
\(f(i,S)=\min (f(i,S),f(i,S')+f(i,S-S'))\)
\(f(i,S)=\min (f(i,S),f(j,S)+w(j,i))\)
还有一种玄学做法
设 \(f(S)\) 是连通了 \(S\) 的最小边权和
\(f(S+S')=min(f(S+S'),f(S)+f(S')+w(S,S'))\)
数据结构
差分&前缀和
高效处理没有修改的前提下的查询
前缀和
复杂度 \(O(MAX)\) 的预处理
有前缀和数组 D[] 原始数组 A[]
D[i]=D[i-1]+A[i];
对于各种要求 有
-
一维 \(query(l,r)=D_r-D_{l-1}\)
-
二维 \(query(x_1,y_1,x_2,y_2)=D_{x_2,y_2}-D_{x_1-1,y_2}-D_{x_2,y_1-1}+D_{x_1-1,y_1-1}\)
更高维的用容斥
-
树上点权 \(query(x,y)=D_x+D_y-D_{lca(x,y)}-D_{fa(lca(x,y))}\)
-
树上边权 \(D_x+D_y-2\times D_{lca(x,y)}\)
单调栈
满足元素单调性的栈
stack<int> st;
//
void insert(int x)
{
while(!st.empty()&&_cmp(st.top(),x)) st.pop();
st.push(x);
}
int query(int x)
{
return st.top();
}
单调队列
满足元素单调性的队列
int q[MAX],id[MAX],head,tail,len; //好像用STL实现比较复杂
//
void init()
{
head=1; tail=0;
}
void insert(int x,int pos)
{
while(head<=tail&&_cmp(q[tail],x)) tail--;
q[++tail]=x; id[tail]=pos;
while(id[head]<=pos-len) head++;
}
并查集
如果加了按秩合并+路径压缩 单次修改 查询 复杂度 \(O(\alpha(n))\)
只有路径压缩 单次修改 查询 复杂度 \(O( \log n)\)
按秩合并策略:将小的树连到大的树上
int f[MAX],size[MAX];
_set(f[i],i); _set(size[i],1)
//
void union(int x,int y) //按秩合并
{
int fx=find(x),fy=find(y);
if(fx==fy) return ;
if(size[fx]>size[fy]) swap(fx,fy);
f[fx]=fy; size[fy]+=size[fx];
}
int find(int x) //路径压缩
{
return f[x]==x?x:f[x]=find(f[x]);
}
堆
STL
priority_queue<T,vector<T>,greater<T> > ; //小根堆
priority_queue<T,vector<T>,less<T> > ; //大根堆(默认)
比较函数
struct node{
data ...;
friend bool operator<(node x,node y) {return x.cmp>y.cmp;} //按照cmp小排的堆(小根)
friend bool operator<(node x,node y) {return x.cmp<y.cmp;} //按照cmp大排的堆(大根)
}
手写
struct Heap{
int h[MAX],size;
void init() {_set(h,size,0);}
void push(int v)
{
int now=v;
h[++size]=v;
while(now)
{
int next=now>>1;
if(_cmp(h[next],h[now])) swap(h[next],h[now]);
else break;
now=next;
}
}
void top() {return h[1];}
void pop()
{
swap(h[1],h[size--]);
int now=1;
while((now<<1)<=size)
{
int next=now<<1;
if(next+1<=size&&_cmp(h[next+1],h[next])) next++;
if(_cmp(h[next,h[now]])) swap(h[next],h[now]);
else break;
now=next;
}
}
};
树状数组
单次修改查询复杂度 \(O(\log n)\)
常数小 跑得比线段数快
单修区查
struct BitTree{
int t[MAX],n;
void init() {_modify(_pos,_need());}
int lowbit(int x) {return x&(-x)};
void modify(int x,int k)
{
while(x<=n) t[x]=_update(t[x],k),x+=lowbit(x);
}
int query(int x)
{
int re=0;
while(x) re=_update(re,t[x]),x-=lowbit(x);
return re;
}
int query(int l,int r)
{
return query(r)-query(l-1);
}
};
区修单查
struct BitTree{
int s[MAX],delta[MAX],n;
void init() {_set(delta,_need()); _set(s);}
void modify(int x,int k)
{
while(x<=n) delta[x]=_update(delta[x],k),x+=lowbit(x);
}
void modify(int l,int r,int k)
{
modify(l,k); modify(r+1,_invOperation(k));
}
int query(int x)
{
return re=0,pos=x;
while(x) re=_update(re,delta[x]),x-=lowbit(x);
return _update(re,s[pos]);
}
};
线段树
单次修改 查询复杂度 \(O(\log n)\)
pushdown() 的时候要注意优先级问题 一般有 覆盖 \(\to\) 乘法 \(\to\) 加法
struct Sgtree{
int init[MAX];
struct node{int l,r,...;}t[MAX<<2];
void init() {_set(init);}
void update(int p) {_update_from_ltree_rtree();}
void pushdown(int p) {_pushdown_to_ltree__rtree();}
void build(int p,int l,int r)
{
t[p].l=l; t[p].r=r; _setnode();
if(l==r)
{
t[p]=_newnode(p);
return ;
}
int mid=(l+r)>>1;
build(p<<1,l,mid); build(p<<1|1,mid+1,r);
update(p);
}
void modify(int p,int l,int r,int k)
{
if(l<=t[p].l&&r>=t[p].r)
{
_modifynode();
return ;
}
pushdown(p);
int mid=(t[p].r+t[p].l)>>1;
if(l<=mid) modify(p<<1,l,mid);
if(r>mid) modify(p<<1|1,mid+1,r);
update(p);
}
int query(int p,int l,int r)
{
if(l>=t[p].l&&r>=t[p].r) return _needVal();
pushdown(p);
int mid=(t[p].l+t[p].r)>>1,re=0;
if(l<=mid) re=_update(re,query(p<<1,l,r));
if(r>mid) re=_update(re,query(p<<1|1,l,r));
return re;
}
};
ST表
\(O(n \log n)\) 建表 \(O(1)\) 查询
其实nlogn建表的时候其它算法已经跑完qlogn了
int log2[MAX],st[MAX][log2[MAX]];
//
void preSolve()
{
log2[0]=-1;
for(int i=1;i<=MAX;i++) log2[i]=log2[i>>1]+1;
for(int i=1;i<=MAX;i++) _get(st[i][0]); //每个点
for(int j=1;j<=log2[MAX];j++)
for(int i=1;i+(1<<j)-1<=n;i++)
st[i][j]=_cmp(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
void RMQ(int l,int r)
{
int t=log2[r-l+1];
return max(st[l][t],st[r-(1<<t)+1][t]);
}
哈希
struct E{int next,key,num,...;}e[MAX];
int last[MAX],cnt=0;
//
int hash(data ...) {return _data_to_hash(...);}
void insert(_data x)
{
int key=hash(x),get=0;
for(int i=last[i];i&&get==0;i=e[i].next)
if(e[i].key==x) e[i].num++,get=1;
if(get==0) e[++cnt]=(E){last[key],x,1,...},last[key]=cnt;
}
int find(_data x)
{
int key=hash(x);
for(int i=last[key];i;i=e[i].next)
if(e[i].key==x) return e[i].num;
return 0;
}
有以下参考思路
-
\(H(x)=\sum x_i\)
-
h(x)=(hash<<4)^(hash>>28)^x[i] -
h(x)=hash*k+x[i]
平衡树
Fhq-Treap
_srand(time(0));
//
struct fhqTreap{
int lc[N],rc[N],val[N],size[N],pri[N],root,tot;
void update(int p)
{
size[p]=size[lc[p]]+size[rc[p]]+1;
}
void splitVal(int now,int k,int &x,int &y)
{
if(!now) x=y=0;
else
{
if(val[now]<=k) x=now,splitVal(rc[now],k,rc[now],y);
else y=now,splitVal(lc[now],k,x,lc[now]);
update(now);
}
}
void splitRank(int now,int k,int &x,int &y)
{
if(!now) x=y=0;
else if(k<=size[lc[now]]) y=now,splitRank(lc[now],k,x,lc[now]);
else x=now,splitRank(rc[now],k-size[lc[now]]-1,rc[now],y);
update(now);
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(pri[x]<pri[y]) return rc[x]=merge(rc[x],y),update(x),x;
else return lc[y]=merge(x,lc[y]),update(y),y;
}
int newnode(int k)
{
size[++tot]=1; val[tot]=k; pri[tot]=rand();
return tot;
}
void insert(int k)
{
int x,y;
splitVal(root,k,x,y);
root=merge(merge(x,newnode(k)),y);
}
void del(int k)
{
int x,y,z;
splitVal(root,k,x,z);
splitVal(x,k-1,x,y);
y=merge(lc[y],rc[y]);
root=merge(merge(x,y),z);
}
int queryValRank(int rt,int k)
{
int x,y;
splitVal(rt,k-1,x,y);
int re=size[x]+1;
root=merge(x,y);
return re;
}
int queryKthVal(int now,int k)
{
while(1)
{
if(k<=size[lc[now]]) now=lc[now];
else if(k==size[lc[now]]+1) return val[now];
else k-=(size[lc[now]]+1),now=rc[now];
}
}
int pre(int k)
{
int x,y;
splitVal(root,k-1,x,y);
int re=queryKthVal(x,size[x]);
root=merge(x,y);
return re;
}
int nxt(int k)
{
int x,y;
splitVal(root,k,x,y);
int re=queryKthVal(y,1);
root=merge(x,y);
return re;
}
};
Splay
#define ls(P) ch[P][0]
#define rs(P) ch[P][1]
#define judgeson(S,F) (rs(F)==S?1:0)
#define update(X) size[X]=size[ls(X)]+size[rs(X)]+num[X]
//
int n,int ch[N][2],fa[N],num[N],data[N],size[N],root,tot;
//
int getrank(int k);
void remove(int x);
int search(int x,int k);
void find(int x);
void insert(int x);
void splay(int x,int goal);
void rotate(int x);
int main()
{
insert(INF);
insert(-INF);
scanf("%d",&n);
while(n--)
{
int opt,x;
scanf("%d%d",&opt,&x);
if(opt==1) insert(x);
if(opt==2) remove(x);
if(opt==3)
{
find(x);
printf("%d\n",size[ls(root)]);
}
if(opt==4) printf("%d\n",getrank(x+1));
if(opt==5) printf("%d\n",data[search(x,0)]);
if(opt==6) printf("%d\n",data[search(x,1)]);
}
return 0;
}
void rotate(int x)
{
int y=fa[x];
int z=fa[y];
int k=judgeson(x,y);
ch[z][judgeson(y,z)]=x; fa[x]=z;
ch[y][k]=ch[x][k^1]; fa[ch[x][k^1]]=y;
ch[x][k^1]=y; fa[y]=x;
update(x); update(y);
}
void splay(int x,int goal)
{
while(fa[x]!=goal)
{
int y=fa[x];
int z=fa[y];
if(z!=goal) judgeson(x,y)^judgeson(y,z)?rotate(x):rotate(y);
rotate(x);
}
if(!goal) root=x;
}
//
void insert(int x)
{
int u=root,f=0;
while(u&&data[u]!=x)
{
f=u;
u=ch[u][x>data[u]?1:0];
}
if(u) num[u]++;
else
{
u=++tot;
if(f) ch[f][x>data[f]?1:0]=u;
ls(tot)=rs(tot)=0;
fa[tot]=f; data[tot]=x;
num[tot]=size[tot]=1;
}
splay(u,0);
}
void remove(int x)
{
int pre=search(x,0),next=search(x,1);
splay(pre,0); splay(next,pre);
int del=ls(next);
if(num[del]>1)
{
num[del]--;
splay(del,0);
}
else ls(next)=0;
}
void find(int x)
{
int u=root;
if(!u) return ;
while(ch[u][x>data[u]?1:0]&&x!=data[u]) u=ch[u][x>data[u]?1:0];
splay(u,0);
}
int search(int x,int k) //next0 pre1
{
find(x);
int u=root;
if((data[u]>x&&k)||(data[u]<x&&!k)) return u;
u=ch[u][k];
while(ch[u][k^1]) u=ch[u][k^1];
return u;
}
int getrank(int k)
{
int u=root;
if(size[u]<k) return 0;
while(1)
{
int y=ls(u);
if(k>size[y]+num[u])
{
k-=size[y]+num[u];
u=rs(u);
}
else if(size[y]>=k) u=y;
else return data[u];
}
}
Hash Map
struct hash_map{
static const int HASH_MAX=0xffffff,N=8000000;
int cnt,head[HASH_MAX+5],next[N]; data z[N];
int getHash(int key)
{
return (key^key<<3^key>>2)&HASH_MAX;
}
void clear()
{
for(;cnt>0;--cnt) head[z[cnt].hash]=0;
}
data* find(int key, bool inserted)
{
int x=getHash(key);
for(int i=head[x];i;i=next[i])
if(z[i].key==key) return z+i;
if(!inserted) return NULL;
z[++cnt]=data(key,0,x); next[cnt]=head[x]; head[x]=cnt;
return z+cnt;
}
};
Link Cut Tree(动态树)
未整理
模板也没到100行
#define lc(X) ch[X][0]
#define rc(X) ch[X][1]
int fa[N],ch[N][2],data[N],rev[N],v[N];
int n,m;
//
void cut(int x,int y);
void link(int x,int y);
void split(int x,int y);
int findroot(int x);
void makeroot(int x);
void access(int x);
void splay(int x);
void rotate(int x);
void pushdown(int x);
void pushrev(int x);
void update(int x);
bool notroot(int x);
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i];
for(int i=1;i<=m;i++)
{
int opt,x,y;
cin>>opt>>x>>y;
if(opt==0) split(x,y),cout<<data[y]<<endl;
if(opt==1) link(x,y);
if(opt==2) cut(x,y);
if(opt==3) splay(x),v[x]=y;
}
return 0;
}
//
void cut(int x,int y)
{
makeroot(x);
if(findroot(y)==x&&fa[y]==x&&!lc(y)) fa[y]=rc(x)=0,update(x);
}
void link(int x,int y)
{
makeroot(x);
if(findroot(y)!=x) fa[x]=y;
}
void split(int x,int y)
{
makeroot(x);
access(y); splay(y);
}
int findroot(int x)
{
access(x); splay(x);
while(lc(x)) update(x),x=lc(x);
splay(x);
return x;
}
void makeroot(int x)
{
access(x); splay(x);
pushrev(x);
}
void access(int x)
{
for(int y=0;x;x=fa[y=x]) splay(x),rc(x)=y,update(x);
}
void splay(int x)
{
int y=x,top=0,s[N];
s[++top]=y;
while(notroot(y)) y=s[++top]=fa[y];
while(top) pushdown(s[top--]);
while(notroot(x))
{
int y=fa[x],z=fa[y];
if(notroot(y)) rotate(((lc(y)==x)^(lc(z)==y)?x:y));
rotate(x);
}
update(x);
}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=(rc(y)==x),w=ch[x][k^1];
if(notroot(y)) ch[z][(rc(z)==y)]=x;
ch[x][k^1]=y; ch[y][k]=w;
if(w) fa[w]=y;
fa[y]=x; fa[x]=z;
update(y);
}
void pushdown(int x)
{
if(rev[x])
{
if(lc(x)) pushrev(lc(x));
if(rc(x)) pushrev(rc(x));
rev[x]=0;
}
}
void pushrev(int x)
{
swap(lc(x),rc(x));
rev[x]^=1;
}
void update(int x)
{
data[x]=data[lc(x)]^data[rc(x)]^v[x];
}
bool notroot(int x)
{
return lc(fa[x])==x||rc(fa[x])==x;
}
左偏树(可并堆)
int n,m,v[N],fa[N],ls[N],rs[N],dis[N],del[N];
//
void pop(int x);
int merge(int x,int y);
int find(int x);
int read();
int main()
{
dis[0]=-1;
n=read(); m=read();
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++) v[i]=read();
for(int i=1;i<=m;i++)
{
int opt,x,y;
opt=read(); x=read();
if(opt==1)
{
y=read();
if(del[x]||del[y]) continue;
x=find(x); y=find(y);
if(x!=y) fa[x]=fa[y]=merge(x,y);
}
if(opt==2)
{
if(del[x])
{
puts("-1");
continue;
}
x=find(x);
printf("%d\n",v[x]);
pop(x);
}
}
return 0;
}
int read()
{
int x=0,w=0; char c=getchar();
while(!isdigit(c))
{
if(c=='-') w=1;
c=getchar();
}
while(isdigit(c))
{
x=x*10+(c^48);
c=getchar();
}
return w?-x:x;
}
int find(int x)
{
return x==fa[x]?x:fa[x]=find(fa[x]);
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(v[x]>v[y]||(x>y&&v[x]==v[y])) swap(x,y);
rs[x]=merge(rs[x],y);
if(dis[ls[x]]<dis[rs[x]]) swap(rs[x],ls[x]);
fa[ls[x]]=fa[rs[x]]=fa[x]=x;
dis[x]=dis[rs[x]]+1;
return x;
}
void pop(int x)
{
del[x]=1;
fa[ls[x]]=ls[x];
fa[rs[x]]=rs[x];
fa[x]=merge(ls[x],rs[x]);
}
珂朵莉树
仅在数据随机的情况下且保证有区间覆盖操作 复杂度为 \(O(n \log n)\)
大部分情况都是 \(O(玄学)\)
#define iter set<node>::iterator
//
struct node{
int l,r; mutable int v;
node(const int L,int R=-1,int V=0):l(L),r(R),v(V){}
bool operator<(const node &x)const {return l<x.l;}
};
set<node> s;
//
iter split(int p)
{
if(p>MAX) return s.end();
iter i=--s.upper_bound(node(p));
if(i->l==p) return i;
int l=i->l,r=i->r,v=i->v;
s.erase(i);
return s.insert(node(l,p-1,v)),s.insert(node(p,r,v)).first;
}
void assign(int l,int r,int v)
{
iter ir=split(r+1),il=split(l);
s.erase(il,ir);
s.insert(node(l,r,v));
}
int performance(int l,int r)
{
iter ir=split(r+1),il=split(l);
for(;il!=ir;++il)
_anything();
}
分块
一般的 取块大小为 \(\sqrt n\)
int s[MAX],block[SIZE];
//
void build()
{
_get(s);
size=_size(MAX);
for(int i=1;i<=n;i++)
{
id[i]=(i-1)/size+1;
_update(block[id[i]],s[i]);
}
}
int action(int l,int r,...)
{
int lid=id[l],rid=id[r];
if(lid==rid)
{
for(int i=l;i<=r;i++) _solve(s[i],block[i]);
return _need();
}
for(int i=l;id[i]==lid;i++) _solve(s[i],block[i]);
for(int i=lid+1;i<rid;i++) _solve(block[i]*size);
for(int i=r;id[i]==rid;i--) _solve(s[i],block[i]);
return _need();
}
可持久化数组
#include<iostream>
#include<cstdio>
#define N 1000500
#define mid ((l+r)>>1)
using namespace std;
int idx,n,m,init[N],root[N];
struct node{int l,r,data;}t[N<<4];
int copy(int p);
int query(int p,int l,int r,int ver);
int modify(int p,int l,int r,int pos,int val);
int build(int p,int l,int r);
void read(int &x);
int main()
{
read(n); read(m);
for(int i=1;i<=n;i++) read(init[i]);
root[0]=build(0,1,n);
for(int i=1;i<=m;i++)
{
int ver,opt,pos,val;
read(ver); read(opt);
if(opt==1)
{
read(pos); read(val);
root[i]=modify(root[ver],1,n,pos,val);
}
if(opt==2)
{
read(pos);
printf("%d\n",query(root[ver],1,n,pos));
root[i]=root[ver];
}
}
return 0;
}
int build(int p,int l,int r)
{
p=++idx;
if(l==r)
{
t[p].data=init[l];
return idx;
}
t[p].l=build(t[p].l,l,mid);
t[p].r=build(t[p].r,mid+1,r);
return p;
}
int modify(int p,int l,int r,int pos,int val)
{
p=copy(p);
if(l==r)
t[p].data=val;
else
{
if(pos<=mid) t[p].l=modify(t[p].l,l,mid,pos,val);
else t[p].r=modify(t[p].r,mid+1,r,pos,val);
}
return p;
}
int query(int p,int l,int r,int pos)
{
if(l==r)
return t[p].data;
if(pos<=mid) return query(t[p].l,l,mid,pos);
else return query(t[p].r,mid+1,r,pos);
}
int copy(int p)
{
t[++idx]=t[p];
return idx;
}
void read(int &x)
{
x=0; bool w=0; char c=getchar();
while(c>'9'||c<'0')
{
if(c=='-') w=1;
c=getchar();
}
while(c>='0'&&c<='9')
{
x=x*10+(c^48);
c=getchar();
}
if(w) x=-x;
}
可持久化并查集
#include<iostream>
#include<cstring>
#define N 200050
#define mid ((l+r)>>1)
#define lc(P) ch[P][0]
#define rc(P) ch[P][1]
using namespace std;
int n,m,fa[N<<4],depth[N<<4];
int ch[N<<4][2],root[N],tot;
int getfa(int id,int x);
void deep(int now,int l,int r,int pos);
int query(int now,int l,int r,int pos);
void modify(int &t1,int t2,int l,int r,int pos,int f);
void build(int &now,int l,int r);
int main()
{
cin>>n>>m;
build(root[0],1,n);
for(int i=1;i<=m;i++)
{
int opt,a,b,k;
cin>>opt;
if(opt==1)
{
cin>>a>>b;
root[i]=root[i-1];
int f1=getfa(root[i],a),f2=getfa(root[i],b);
if(fa[f1]==fa[f2]) continue;
if(depth[f1]>depth[f2]) swap(f1,f2);
modify(root[i],root[i-1],1,n,fa[f1],fa[f2]);
if(depth[f1]==depth[f2]) deep(root[i],1,n,fa[f2]);
}
if(opt==2)
{
cin>>k;
root[i]=root[k];
}
if(opt==3)
{
cin>>a>>b;
root[i]=root[i-1];
if(fa[getfa(root[i],a)]!=fa[getfa(root[i],b)]) cout<<0<<endl;
else cout<<1<<endl;
}
}
return 0;
}
void build(int &now,int l,int r)
{
now=++tot;
if(l==r)
{
fa[now]=l;
return ;
}
build(lc(now),l,mid);
build(rc(now),mid+1,r);
}
void modify(int &t1,int t2,int l,int r,int pos,int f)
{
t1=++tot;
if(l==r)
{
fa[t1]=f;
depth[t1]=depth[t2];
return ;
}
lc(t1)=lc(t2); rc(t1)=rc(t2);
if(pos<=mid) modify(lc(t1),lc(t2),l,mid,pos,f);
else modify(rc(t1),rc(t2),mid+1,r,pos,f);
}
int query(int now,int l,int r,int pos)
{
while(l!=r)
{
if(pos<=mid)
{
now=lc(now); r=mid;
}
else
{
now=rc(now); l=mid+1;
}
}
return now;
}
void deep(int now,int l,int r,int pos)
{
while(l!=r)
{
if(pos<=mid)
{
now=lc(now); r=mid;
}
else
{
now=rc(now); l=mid+1;
}
}
depth[now]++;
}
int getfa(int id,int x)
{
int f;
while(x!=fa[f=query(id,1,n,x)])
x=fa[f];
return f;
}
主席树(可持久化线段树)
求解静态区间第 \(k\) 小
#include<iostream>
#include<algorithm>
#define N 200050
#define ls(P) ch[P][0]
#define rs(P) ch[P][1]
using namespace std;
int n,m,cnt,tot;
int a[N],map[N];
int ch[N<<5][2],size[N<<5],root[N];
struct node{int num,id;}init[N];
bool cmp(const node &x,const node &y);
int build(int l,int r);
int update(int pre,int l,int r,int x);
int query(int u,int v,int l,int r,int x);
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>init[i].num;
init[i].id=i;
}
sort(init+1,init+n+1,cmp);
init[0].num=2147483647;
for(int i=1;i<=n;i++)
{
if(init[i].num!=init[i-1].num) cnt++;
a[init[i].id]=cnt;
map[cnt]=init[i].num;
}
//
root[0]=build(1,cnt);
for(int i=1;i<=n;i++)
root[i]=update(root[i-1],1,cnt,a[i]);
for(int i=1;i<=m;i++)
{
int l,r,x;
cin>>l>>r>>x;
cout<<map[query(root[l-1],root[r],1,cnt,x)]<<endl;
}
return 0;
}
bool cmp(const node &x,const node &y)
{
return x.num<y.num;
}
int build(int l,int r)
{
int now=++tot;
if(l<r)
{
int mid=(l+r)>>1;
ls(now)=build(l,mid);
rs(now)=build(mid+1,r);
}
return now;
}
int update(int pre,int l,int r,int x)
{
int now=++tot;
size[now]=size[pre]+1;
ls(now)=ls(pre);
rs(now)=rs(pre);
if(l<r)
{
int mid=(l+r)>>1;
if(x>mid) rs(now)=update(rs(now),mid+1,r,x);
else ls(now)=update(ls(now),l,mid,x);
}
return now;
}
int query(int u,int v,int l,int r,int x)
{
if(l==r) return l;
int sz=size[ls(v)]-size[ls(u)];
int mid=(l+r)>>1;
if(x<=sz) return query(ls(u),ls(v),l,mid,x);
else return query(rs(u),rs(v),mid+1,r,x-sz);
}
可持久化平衡树
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
#define N 500050
#define log 50
#define INF 2147483647
#define lc(NOW) ch[NOW][0]
#define rc(NOW) ch[NOW][1]
using namespace std;
int size[N*log],ch[N*log][2],pri[N*log],data[N*log],tot;
int ver[N],n;
void read(int &x);
int getval(int now,int k);
int getrank(int &ver,int val);
int getpre(int &ver,int val);
int getsuc(int &ver,int val);
void remove(int &ver,int val);
void insert(int &ver,int val);
int copy(int now);
void newnode(int val);
int merge(int x,int y);
void split(int now,int val,int &x,int &y);
void update(int now);
int read();
int main()
{
srand(time(0));
read(n);
for(int i=1;i<=n;i++)
{
int v,opt,x;
read(v); read(opt); read(x);
ver[i]=ver[v];
if(opt==1) insert(ver[i],x);
if(opt==2) remove(ver[i],x);
if(opt==3) printf("%d\n",getrank(ver[i],x));
if(opt==4) printf("%d\n",getval(ver[i],x));
if(opt==5) printf("%d\n",getpre(ver[i],x));
if(opt==6) printf("%d\n",getsuc(ver[i],x));
}
return 0;
}
void newnode(int &now,int val)
{
data[now=++tot]=val;
size[now]=1;
pri[now]=rand();
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
if(pri[x]>pri[y])
{
int idx=copy(x);
rc(idx)=merge(rc(idx),y);
update(idx);
return idx;
}
else
{
int idx=copy(y);
lc(idx)=merge(x,lc(idx));
update(idx);
return idx;
}
}
void split(int now,int val,int &x,int &y)
{
if(!now)
{
x=y=0;
return ;
}
if(data[now]<=val)
{
x=copy(now);
split(rc(x),val,rc(x),y);
update(x);
}
else
{
y=copy(now);
split(lc(y),val,x,lc(y));
update(y);
}
}
void update(int now)
{
size[now]=size[lc(now)]+size[rc(now)]+1;
}
int copy(int now)
{
size[++tot]=size[now];
lc(tot)=lc(now); rc(tot)=rc(now);
pri[tot]=pri[now];
data[tot]=data[now];
return tot;
}
void remove(int &ver,int val)
{
int x,y,z;
split(ver,val,x,z);
split(x,val-1,x,y);
y=merge(lc(y),rc(y));
ver=merge(merge(x,y),z);
}
void insert(int &ver,int val)
{
int x,y,z;
split(ver,val,x,y);
newnode(z,val);
ver=merge(merge(x,z),y);
}
int getval(int now,int k)
{
while(k!=size[lc(now)]+1)
{
if(k<=size[lc(now)]) now=lc(now);
else
{
k-=size[lc(now)]+1;
now=rc(now);
}
}
return data[now];
}
int getrank(int &ver,int val)
{
int x,y,re;
split(ver,val-1,x,y);
re=size[x]+1;
ver=merge(x,y);
return re;
}
int getpre(int &ver,int val)
{
int x,y,re;
split(ver,val-1,x,y);
if(!x) return -INF;
re=getval(x,size[x]);
ver=merge(x,y);
return re;
}
int getsuc(int &ver,int val)
{
int x,y,re;
split(ver,val,x,y);
if(!y) return INF;
re=getval(y,1);
ver=merge(x,y);
return re;
}
void read(int &x)
{
x=0; bool w=0;
char c=getchar();
while(!isdigit(c))
{
if(c=='-') w=1;
c=getchar();
}
while(isdigit(c))
{
x=x*10+(c^48);
c=getchar();
}
if(w) x=-x;
}
二逼平衡树(树套树)
fhq_treap套线段树....
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN = 5e4 + 5, MAXM = 1e7;
const int INF = 0x7fffffff;
int a[MAXN];
int n, m;
int val[MAXM], sz[MAXM], heap[MAXM], l[MAXM], r[MAXM];
int tot;
struct FHQ_Treap
{
int root;
inline void Update(int x)
{
sz[x] = sz[l[x]] + sz[r[x]] + 1;
return;
}
inline int Merge(int x, int y)
{
if (!x || !y)
return x + y;
if (heap[x] < heap[y])
return r[x] = Merge(r[x], y), Update(x), x;
return l[y] = Merge(x, l[y]), Update(y), y;
}
inline void Split(int x, int key, int &u, int &v)
{
if (!x)
{
u = v = 0;
return;
}
if (val[x] <= key)
u = x, Split(r[x], key, r[u], v);
else
v = x, Split(l[x], key, u, l[v]);
Update(x);
return;
}
inline int Create(int key)
{
val[++tot] = key, heap[tot] = rand(), sz[tot] = 1;
return tot;
}
int x, y, z;
inline void Insert(int key)
{
Split(root, key, x, y);
root = Merge(x, Merge(Create(key), y));
return;
}
inline void Delete(int key)
{
Split(root, key, x, z);
Split(x, key - 1, x, y);
y = Merge(l[y], r[y]);
root = Merge(x, Merge(y, z));
return;
}
inline int FindRank(int key)
{
Split(root, key - 1, x, y);
int ans = sz[x] + 1;
root = Merge(x, y);
return ans;
}
inline void Build(int l, int r)
{
for (int i = l; i <= r; i++)
Insert(a[i]);
return;
}
inline int FindKey(int x, int rak)
{
if (rak <= sz[l[x]])
return FindKey(l[x], rak);
if (rak == sz[l[x]] + 1)
return val[x];
return FindKey(r[x], rak - sz[l[x]] - 1);
}
inline int Lower(int key)
{
Split(root, key - 1, x, y);
int ans;
if (sz[x])
ans = FindKey(x, sz[x]);
else
ans = -INF;
root = Merge(x, y);
return ans;
}
inline int Upper(int key)
{
Split(root, key, x, y);
int ans;
if (sz[y])
ans = FindKey(y, 1);
else
ans = INF;
root = Merge(x, y);
return ans;
}
} FT[MAXN << 2];
#define mid ((x + y) >> 1)
#define lson (pst << 1)
#define rson (pst << 1 | 1)
struct SegmentTree
{
int root[MAXN << 2];
inline void Build(int x, int y, int pst)
{
FT[pst].Build(x, y);
if (x != y)
Build(x, mid, lson), Build(mid + 1, y, rson);
return;
}
inline int QueryRank(int x, int y, int pst, int l, int r, int key)
{
if (y < l || x > r)
return 0;
if (l <= x && y <= r)
return FT[pst].FindRank(key) - 1;
return QueryRank(x, mid, lson, l, r, key) + QueryRank(mid + 1, y, rson, l, r, key);
}
inline int QueryKey(int l, int r, int rak)
{
int x = 0, y = 1e8, ans = -1;
while (x <= y)
{
if (QueryRank(1, n, 1, l, r, mid) + 1 <= rak)
ans = mid, x = mid + 1;
else
y = mid - 1;
}
return ans;
}
inline void Update(int x, int y, int pst, int p, int k)
{
FT[pst].Delete(a[p]);
FT[pst].Insert(k);
if (x != y)
{
if (p <= mid)
Update(x, mid, lson, p, k);
else
Update(mid + 1, y, rson, p, k);
}
return;
}
inline int Lower(int x, int y, int pst, int l, int r, int k)
{
if (y < l || x > r)
return -INF;
if (l <= x && y <= r)
return FT[pst].Lower(k);
return max(Lower(x, mid, lson, l, r, k), Lower(mid + 1, y, rson, l, r, k));
}
inline int Upper(int x, int y, int pst, int l, int r, int k)
{
if (y < l || x > r)
return INF;
if (l <= x && y <= r)
return FT[pst].Upper(k);
return min(Upper(x, mid, lson, l, r, k), Upper(mid + 1, y, rson, l, r, k));
}
} ST;
signed main(void)
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
scanf("%d", a + i);
ST.Build(1, n, 1);
for (int i = 1, opt, l, r, p, k; i <= m; i++)
{
scanf("%d", &opt);
if (opt == 1)
{
scanf("%d %d %d", &l, &r, &k);
printf("%d\n", ST.QueryRank(1, n, 1, l, r, k) + 1);
}
if (opt == 2)
{
scanf("%d %d %d", &l, &r, &k);
printf("%d\n", ST.QueryKey(l, r, k));
}
if (opt == 3)
{
scanf("%d %d", &p, &k);
ST.Update(1, n, 1, p, k);
a[p] = k;
}
if (opt == 4)
{
scanf("%d %d %d", &l, &r, &k);
printf("%d\n", ST.Lower(1, n, 1, l, r, k));
}
if (opt == 5)
{
scanf("%d %d %d", &l, &r, &k);
printf("%d\n", ST.Upper(1, n, 1, l, r, k));
}
}
return 0;
}
动态规划
dp
动态规划常常适用于有重叠子问题和最优子结构性质的问题
可用动态规划求解的问题需要有以下结构
- 无后效性 状态空间应构成一个有向无环图,即已经求解阶段应不受后续阶段的影响
- 最优子结构 假设问题的最优解所包括的子问题的解也是最优的,即下一阶段最优解应能从已求解阶段得出
- 子问题重叠 前面的状态会被多次计算使用
则可通过状态转移方程描述各阶段之间的性质
最优子结构
证明问题最优解的第一个组成部分是做出一个选择;
对于一个给定问题,在其可能的第一步选择中,你界定已经知道哪种选择才会得到最优解。你现在并不关心这种选择具体是如何得到的,只是假定已经知道了这种选择;
给定可获得的最优解的选择后,确定这次选择会产生哪些子问题,以及如何最好地刻画子问题空间;
证明作为构成原问题最优解的组成部分,每个子问题的解就是它本身的最优解。方法是反证法,考虑加入某个子问题的解不是其自身的最优解,那么就可以从原问题的解中用该子问题的最优解替换掉当前的非最优解,从而得到原问题的一个更优的解,从而与原问题最优解的假设矛盾。
体现
- 原问题的最优解中涉及多少个子问题;
- 确定最优解使用哪些子问题时,需要考察多少种选择。
重叠子问题
子问题空间要足够小,即问题的递归算法会反复地求解相同的子问题,而不是一直生成新的子问题。
重构最优解
存表记录最优分割的位置,就不用重新按照代价来重构。
通过迭代考虑子问题,将问题规模减小而最终解决问题
记忆化搜索
优点
- 记忆化搜索可以避免搜到无用状态,特别是在有状态压缩时
- 边界情况非常好处理,且能有效防止数组访问越界
- 有些 dp(如区间 dp) 用记忆化搜索写很简单但正常 dp 很难
- 记忆化搜索天生携带搜索天赋,可以使用技能 "剪枝"!
缺点
- 不能滚动数组
- 有些优化比较难加
- 由于递归,有时效率较低但不至于 TLE(状压 dp 除外)
板子
int f[MAX];
_set(f,_wasteVal());
//
int solve(_data state)
{
if(f[state]!=_wasteVal()) return f[state];
if(_end()) return _minSolve();
if(_canPrune()) return _pruneVal();
f[state]=solve(_downsizeState());
return f[state];
}
线性dp
LIS 最长上升子序列
设 \(f(i)\) 表 以 \(i\) 结尾的最长的上升子序列的长度
\(f(i)=\max(f(i),f(j)+val(j,i)) \qquad j \in [0,i)\)
LCS 最长公共子序列
设 \(f(i,j)\) 表 前缀子串 \(A_{1 \sim i}\) \(B_{1 \sim j}\) 的最长公共子序列的长度
DAG上dp
建图
有图 \(G=(V,E)\)
可以将一个多元组 \((p_1,p_2,...,p_n)\) 看成一个点 \(V'\)
可以将 \(\mathrm{dis}(V,V')\) 看成一条带权边 \(E'\)
建图
转移
设 \(f(x)\) 表 在图上节点 \(x\) 的时候的最优解的值
\(w(x)\) 表点权 \(dis(x\to y)\) 表边 \(u\to v\) 的权值
\(f(u)=\max(f(v)+dis(u\to v)+w(v)) \qquad v\in son_u\)
背包dp
有 崔添翼"背包九讲" 已经很完善了
区间dp
阶段可由前阶段合并而来
设 \(f(i,j)\) 表 下标 \(i\) 至 \(j\) 的所有元素合并可得的最大价值
\(f(i,j)=\max(f(i,k)+f(k+1,j)+mergeCost((i,k), (k+1,j))) \qquad k\in (i,j)\)
处理环
将一条长为 \(n\) 的环断成一条长为 \(2n\) 的链 其中 \(i\) 和 \(i+n\) 相同
树形dp
可用 自顶向下的递归 或 自底向上的拓扑排序 执行树形dp
基础
设 \(f(x)\) 表 以 \(x\) 为根的子树的最优解
\(f(u)=\max(f(v))+val(u) \qquad v\in son_u\)
树上背包
设 \(f(u,i,j)\) 表 已遍历了 \(u\) 节点的前 \(i\) 棵子树 选择 \(j\) 的最大值
\(size_x\) 表以 \(x\) 为根的节点的子树中节点的个数
\(f(u,i,j)=\max(f(u,i-1,j-k)+f(v,size_v-1,k)) \qquad v,k\in [i,j],k\in [1,size_v]\)
换根与二次扫描
根的选取会对节点值产生一定影响,如 子结点深度和 点权和
第一次 \(\mathrm{DFS}\) 任取一个点为根 执行树形dp 预处理信息
第二次 \(\mathrm{DFS}\) 执行一次深度优先遍历 计算出换根后的值
如 记 \(depSum(x)\) 表 以 \(x\) 为根的子树的深度和
\(size_x\) 表以 \(x\) 为根的节点的子树中节点的个数, \(tot\) 表点的总数
有 \(depSum(u)=depSum(u)+tot-2 \times size_v\)
换根顺序 \(f(u)\to f(v)\)
状态压缩dp
可将阶段的状态看作一个 \(K\) 进制的集合 \(S\) ,每个数的取值为 \([0,K)\) ,记为dp的一维
一般的 有取 \(K=2\) 这样 一般有 \(MAX \le 23\)
参考转移方程(状压dp比较灵活)
\(f(S)=\max(f(S')+cost(S'\to S))\)
\(f(S \ or \ S')=\max(f(S)+f(S')+mergeCost(S' , S))\)
数位dp
给定一个闭区间 \([l,r]\) 求解满足某种条件的数的个数
可设 \(f(i)\) 表 \([1,i]\) 中满足条件的数的个数 则 \(ans=f(r)-f(l-1)\)
有
设 \(f(i,st,op)\) 表 当前考虑为从高到低的第 \(i\) 位 当前前缀状态为 \(st\) 且前缀关系为 \(op=1/0\) 的数字个数
\(f(i,st,op)=\sum\limits_{k=1}^{xMax}f(i+1,k,op=1 \ \mathrm{and} \ k=xMax ) \qquad |st-k|\ge 2\)
概率dp
一般都为倒推
设 \(f(x)\) 是到 \(x\) 状态还剩多少
最后答案即为 \(f(0)\)
可能还需要个高斯消元
数学
小学数学
快速幂
求解 \(a^k\equiv c \mod p\) 中的 \(c\)
int qpow(int a,int k,int p) //a^k (mod p)
{
int re=1;
while(k)
{
if(k&1) re=re*a%p;
a=a*a%p; k>>=1;
}
return re%p;
}
//另一种写法
int qpow(int a,int b,int p)
{
int re=1;
for(;b;b>>=1,a=a*a%p)
if(b&1) re=re*a%p;
return re%p;
}
推理与证明
归纳公理
设 \(S\) 是 \(\N\) 的一个子集,满足
- \(1\in S\)
- 如果 \(n \in S\) 则 \(n+1 \in S\)
那么 \(S=\N\)
:这里 '\(+\)' 是用后继定义的
第一数学归纳法
设 \(P(n)\) 是一个含自然数 \(n\) 的命题,有
- 验证 \(n=n_0(n_0 \in N^*)\) 时,命题 \(P(n_0)\) 成立
- 假设 \(n=k(k\in N^* ,k \ge n_0)\) 时,命题 \(P(k)\) 成立,能推出 \(n=k+1\) 时,命题 \(P(k+1)\) 也成立
根据上述两步,知对于一切自然数 \(n(n\ge n_0)\) ,命题 \(P(n)\) 都成立
第二数学归纳法
设 \(P(n)\) 是一个含自然数 \(n\) 的命题,有
- 验证 \(n=n_0(n_0 \in N^*)\) 时,命题 \(P(n_0)\) 成立
- 假设对于所有适合的 \(n_0 \le n \le k\) 的自然数 \(n\) ,命题 \(P(n)\) 成立,能推出 \(P(k+1)\) 也成立
根据上述两步,知对于一切自然数 \(n(n\ge n_0)\) ,命题 \(P(n)\) 都成立
反向数学归纳法
设 \(P(n)\) 是一个含有自然数 \(n\) 的命题,若
- \(P(n)\) 对无限多个自然数 \(n\) 成立
- 假设 \(n=k\) 时,命题 \(P(k)\) 成立,能推出当 \(n=k-1\) 时,命题 \(P(k-1)\) 也成立
则对于一切自然数 \(n\) ,命题 \(P(n)\) 成立
螺旋数学归纳法
设 \(P(n)\) 和 \(Q(n)\) 是两个与自然数 \(n\) 有关的命题,若
- 命题 \(P(1)\) 成立
- 对于任何自然数 \(k\) ,若 \(P(k)\) 成立,则 \(Q(k)\) 成立;又若 \(Q(k)\) 成立 ,则 \(P(k+1)\) 成立
则对于所有自然数 \(n\) ,命题 \(P(n)\) 和 \(Q(n)\) 都成立
不完全数学归纳法
- 从符合题设的最小基数 \(n=n_0\) 入手,探索 \(n=n_0,n_0+1,n_0+2...\) 几个特例的结果
- 由这几个结果,发现,寻找,总结其规律,并对一般的自然数 \(n\) 提出一个"猜测"
- 利用数学归纳法论证"猜测"的正确性
显然数学归纳法
设 \(f(n)\) 是一个命题
由观察得知,显然对于一切数 \(n\) ,均有 \(f(n)\) 成立
递归数列
有形如
-
\(a_{n+1}=a_n+f(n)\)
\(a_n=a_1+\sum\limits_{k=2}^{n}(a_k-a_{k-a})=a_1+\sum\limits_{k=2}^{n}f(k-1)=a_1+\sum\limits_{k=1}^{n-1}f(k)\)
-
\(a_{n+1}=f(n)a_n\)
有 \(\frac{a_n}{a_{n-1}}=f(n-1)\) 则
\(a_n=a_1 \prod\limits_{k=1}^{n-1}f(k)(n\ge 2)\)
-
\(a_{n+1}=pa_n+q(p \ne 1)\)
有 \(a_{n+1}-a_n=p(a_n-a_{n-1})\) 有
\(a_n=a_q+\sum\limits_{k=2}^{n}(a_k-a_{k-1})=a_1+\frac{(a_2-a_1)(1-p^{n-1})}{1-p}(n \ge 2)\)
-
\(a_{n+1}=pa_n+q(n)\)
有 \(\frac{a_{n+1}}{p^{n+1}}=\frac{a_n}{p^n}+\frac{q(n)}{p^{n+1}}\) 则
令 \(b_n=\frac{a_n}{p^n}\) 有 \(b_{n+1}=b_n+\frac{q(n)}{p^{n+1}}\)
则可化为 \(1\)
-
\(a_{n+1}=pa_n^q\)
有 \(\lg a_{n+1}=q\lg a_n+\lg p\) 则
令 \(b_n=\lg a_n\) 有 \(b_{n+1}=qb_n+\lg p\)
则可化为 \(3\)
-
\(a_{n+1}=pa_n+qa_{n-1}\)
-
如 \(p+q=1\) 有
\(a_{n+1}-a_n=(-q)^{n-1}(a_2-a_1)\) 即
\(a_{n+1}=a_n+(-q)^{n-1}(a_2-a_1)\)
则可化为 \(3\)
-
如 \(p+q\ne 1\) 存在 \(\alpha , \beta\) 满足
\(a_{n+1}-\alpha a_n=\beta(a_n-\alpha a_{n-1})\) 整理得
\(a_{n+1}=(\alpha +\beta)a_b-\alpha \beta a_{n-1}\)
则 \(\alpha+\beta=p,\alpha \beta=-q\)
解出 \(\alpha,\beta\) 后 再求出 \((a_{n+1}-\alpha a_{n})\) 求解
-
质数
素数分布定理
设 \(\pi(x)\) 是不超过 \(x\) 的素数的个数 则 \(\pi(x) \sim \dfrac{x}{\ln x}\)
算术基本定理
任何一个大于 \(1\) 的正整数都能有限分解为有限个质数的乘积 即 \(N=p_1^{c_1}p_2^{c_2}...p_m^{c_m}\)
其中 \(c_i\) 为正整数 \(p_i\) 为质数 且满足 \(p_1<p_2<...<p_m\)
算术基本定理的推论
有 \(N\) 的正约数集合 \((p_1^{b_1}p_2^{b_2}...p_m^{b_m})\) 其中 \(0\le b_i\le c_i\)
有 \(N\) 的正约数个数 \((c_1+1)\times (c_2+1)\times ... \times (c_m+1)=\prod\limits_{i=1}^{m}(c_i+1)\)
有 \(N\) 的所有正约数的和 \((1+p_1+...+p_1^{c_1})\times ... \times (1+p_m+...+p_m^{c_m})=\prod\limits_{i=1}^{m}(\sum\limits_{j=0}^{c_i}(p_i)^j)\)
Eratosthenes 筛法
复杂度 \(O(n \ln \ln \sqrt n+o(n))\)
bitset<N+1> isPrime;
isPrime.set(); //这行作用是全部设为true
//
void getPrime()
{
isPrime[0]=isPrime[1]=false;
for(int i=2;i*i<=N;i++)
if(isPrime[i]) for(int j=i*i;j<=N;j+=i) isPrime[j]=false;
}
最大公约数
最大公约数定理
有 \(\forall a,b \in \N , gcd(a,b)\times lcm(a,b)=a\times b\)
有时 也记 \((a,b)\) 为 \(gcd(a,b)\) 记 \([a,b]\) 为 \(lcm(a,b)\)
有 \((a,b)[a,b]=ab\)
辗转相除法
复杂度 \(O(\log n)\)
求 \(gcd(x,y)=c\) 的 \(c\)
int gcd(int x,int y)
{
return y==0?x:gcd(y,x%y);
}
位运算加速(有一定限制 \(x,y\) 不能为 \(0\) )
int gcd(int x,int y)
{
while(y^=x^=y^=x%=y) ;
return x;
}
裴蜀定理
设 \(a,b\) 为不全为 \(0\) 的整数 则存在整数 \(x,y\) 使得 \(ax+by=\gcd(a,b)\)
拓展欧几里德算法
给出不定方程 \(ax+by=\gcd(a,b)\) 求出其一组特解 并返回 \(\gcd\)
int exgcd(int a,int b,int &x,int &y)
{
if(b==0) return x=1,y=0,a;
else
{
exgcd(b,a%b,x,y);
int d=x;
x=y; y=d-a/b*y;
return d;
}
}
求解 ax+by=c
显然 如果 \(c\) 不是 \(\gcd(a,b)\) 的倍数直接无解
已知 \(\mathrm{exgcd}\) 可以求出 \(ax+by=gcd(a,b)\) 一组特解 \(x_0\) 与 \(y_0\)
则有 \(a\dfrac{x_0c}{\gcd(a,b)}+b\dfrac{y_0c}{\gcd(a,b)}=c\)
则得到了原方程一组特解 \(x_1=\dfrac{x_0c}{\gcd(a,b)},y_1=\dfrac{y_0c}{\gcd(a,b)}\)
设 \(d \in \Q\) 则有 \(a(x_1+db)+b(y_1-da)=c\)
因为 \(x_1,y_1 \in \Z\) 所有 \(db,da \in \Z\)
则设 \(d\) 可取最小正值有 \(d_x=db,d_y=da\) 则有 \(d_x=\dfrac{b}{\gcd(a,b)},d_y=\dfrac{a}{\gcd(a,b)}\) 在 \(d=\dfrac{1}{\gcd(a,b)}\) 时取到
则有通解 \(x=x_1+sd_x,y=y_1-sd_y\) 其中 \(s\in \Z\)
同余
费马小定理
若 \(p\) 为质数 则对于任意整数 \(a\) 有 \(a^p \equiv a\mod p\)
但是 满足 \(a^p \equiv a \mod p\) 的数 \(p\) 不一定是一个质数
欧拉定理
若 \(a,m\) 互质 则 \(a^{\varphi(m)}\equiv 1 \mod m\)
扩展欧拉定理
逆元
线性求逆元
要求 \(p\) 为质数
int inv[MAX];
//
void getInv()
{
inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=(p-p/i)*inv[p%i]%p;
}
拓展欧几里得求逆元
即求 \(ax+py\equiv1 \mod p\) 的一组整数解
要求 \(gcd(a,p)=1\)
则 \(x\) 为一个解 所以 \(a^{-1}=x\)
exgcd(a,p,x,y);
求出来的逆元可能不是正值 要 (x+p)%p
费马小定理求逆元
有 \(a^{p-1}\equiv 1 \mod p\) 要求 \(p\) 为质数
所以 \(a^{-1}=a^{p-2}\)
invA=qpow(a,p-2,p);
欧拉函数
欧拉函数
定义 \(\varphi(n)\) 即小于等于 \(n\) 的与 \(n\) 互相质的数的个数
性质:
- \(\varphi(1)=1\)
- 当 \(n\) 为质数 \(\varphi(n)=n-1\)
- \(\varphi(n)\) 是积性函数 则若 \(a,b\) 互质 \(\varphi(ab)= \varphi(a)\varphi(b)\)
- 对于 \(\forall n>1\) 在 \(1\sim n\) 中与 \(n\) 互质的数的和为 \(\frac{n\times \varphi(n)}{2}\)
对于一个数 \(n\) 有 \(\varphi(n)=n\times \frac{p_1-1}{p_1}\times ...\times \frac{p_m-1}{p_m}=n\times \prod\limits_{p|n}(1-\frac{1}{p})\) 其中 \(p\) 为质数
筛法
有 \(\varphi(n)=\varphi(p)\times\varphi(n')=(p-1)\times\varphi(n')\)
int phi[MAX];
//
void getPhi()
{
_set(phi,0);
phi[1]=1;
for(int i=2;i<=n;i++)
if(!phi[i])
for(int j=i;j<=n;j+=i)
{
if(!phi[j]) phi[j]=j;
phi[j]=phi[j]/i*(i-1);
}
}
线性基
给出一个集合 \(A\) , 令 \(c=c\ xor \ x_i \quad x_i \in A\) , 求最大的 \(c\)
long long A[MAX],bit[65],ans;
//
void getBit(int x)
{
for(int i=62;i;i--)
{
if(!(x>>(ll)i)) continue;
if(!bit[i])
{
bit[i]=x;
break;
}
x^=bit[i];
}
}
int solve()
{
for(int i=1;i<=MAX;i++) _get(A);
for(int i=1;i<=MAX;i++) getBit(A[i]);
ans=0;
for(int i=62;i;i--)
if((ans^bit[i])>ans) ans^=bit[i];
return ans;
}
同余方程
线性同余方程
给出 \(a,b,m\) 求一个整数 \(x\) 满足 \(ax \equiv b \mod m\)
原式可转为 \(ax+my=b\) 则有
\(ax+my=b \iff a\frac{x_0b}{(a,m)}+b\frac{y_0b}{(a,m)}=b\) 当且仅当 \((a,m)|b\) 有整数解
则有一组特解 \(x_0=x'\times \frac{b}{(a,m)},y_0=y'\times \frac{b}{(a,m)}\)
有通解 \(x=x_0+k\frac{m}{(a,m)},y=y_0+k\frac{a}{(a,m)} \quad k\in \Z\)
中国剩余定理
设 \(m_1,m_2,...,m_n\) 是两两互质的整数 令 \(m=\prod\limits_{i=1}^{n} m_i,M_i=m/m_i\) \(t_i\) 是方程 \(M_it_i \equiv 1 \mod m_i\) 的一个解
则 对于任意的 \(n\) 个整数 \(a_1,a_2,...,a_n\) 方程组
有整数解 为 \(x=\sum\limits_{i=1}^{n} a_iM_it_i\)
#include<iostream>
#define ll long long
const int N=1e6+50;
using namespace std;
ll n,m[N],mul,a[N],mi[N],ans;
void exgcd(ll a,ll b,ll &x,ll &y);
int main()
{
cin>>n;
mul=1;
for(int i=1;i<=n;i++) cin>>m[i]>>a[i],mul*=m[i];
for(int i=1;i<=n;i++)
{
mi[i]=mul/m[i];
ll x=0,y=0;
exgcd(mi[i],m[i],x,y);
ans+=a[i]*mi[i]*(x<0?x+m[i]:x);
}
cout<<ans%mul;
return 0;
}
void exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0) x=1,y=0;
else
{
exgcd(b,a%b,x,y);
ll t=x; x=y;
y=t-y*(a/b);
}
}
拓展中国剩余定理
给出数列 \(a_1,a_2,...,a_n\) 和 数列 \(m_1,m_2,...,m_n\) 求一个最小正整数 \(x\) 满足 \(\forall i\in[i,n]\) 有 \(x\equiv a_i \mod m_i\)
或者判断无解 与上不同的是 \(m_i\) 不一定两两互质
考虑数学归纳法
假设已经求出了前 \(k-1\) 个方程构成的方程组的解 \(x\) 记 \(m=\prod\limits_{i=1}^{k-1} m_i\) 则 \(x+qm,q\in \Z\) 是方程的通解
考虑加入第 \(k\) 个方程 现要求求出一个整数 \(t\) 使 \(x+tm \equiv a_k \mod m_k\)
该方程等价于 求解 \(tm \equiv a_k-x \mod k\) 的解 \(t\)
此时可求出解或判断有无解
新方程组的解即为 \(x'=x+tm\)
则反复使用 \(n\) 次拓展欧几里得算法即可求解整个方程组
二次剩余
给出 \(a,p\) 求解 \(x^2 \equiv a \mod p\) 的方程
仅讨论 \(p\) 为奇素数的情况(非 \(2\) )
BSGS
给出 \(b,p,n\) 求 形如 \(b^l =n \mod p\) 时 \(l\) 的值
#define ll long long
//
ll qpow(ll a,ll k,ll p); //快速幂
//
ll BSGS(ll p,ll有 b,ll n)
{
map<ll,ll> h;
ll now=n%p;
ll m=ceil(sqrt(p));
for(int i=1;i<=m;i++)
{
now*=b; now%=p;
if(!h[now]) h[now]=i;
}
now=qpow(b,m,p);
ll tmp=now;
for(int i=1;i<=m;i++)
if(h[now]) return (i*m-h[now]%p+p)%p;
else now*=tmp,now%=p;
return -1;
}
矩阵
由 \(n\times m\) 个数 \(a_{i,j}\) 排成的 \(m\) 行 \(n\) 列的数表
注:矩阵本身并不代表任何意义,甚至可以看作一个二维数组,但是,可以人为赋予意义或从多个角度看待矩阵
可以说 矩阵是描述抽象事物的工具
特别的 当 \(n=m\) 时 可以称为方阵 当 \(n=1\) 或 \(m=1\) 时 可以看作向量
如果一个矩阵的元素可以被一个只与行 \(i\) 列 \(j\) 有关的统一函数 \(f\) 表示 也可记为 \((f(i,j))_{m \times n}\)
运算
一个 \(n\times m\) 的矩阵可以看作 \(n\times m\) 的二维数组
有三个矩阵 \(A,B,C\) 有
加减法
\(C=A \pm B \iff \forall i\in [1,n],\forall j\in[1,m],C_{i,j}=A_{i,j} \pm B_{i,j}\)
即有
乘法
数乘
\(\lambda A \iff \forall i\in [1,n],\forall j\in[1,m],A_{i,j}=\lambda A_{i,j}\)
即有
矩阵相乘
设 \(A\) 是 \(n \times m\) 的矩阵 \(B\) 为 \(m \times p\) 的矩阵
则 \(C=AB\) 是 \(n\times p\) 的矩阵 仅有 \(A\) 的列数(行数)与 \(B\) 的行数(列数)相等的时候才能求
\(C=A B \iff \forall i\in [1,n],\forall j\in[1,p],C_{i,j}=\sum\limits_{k=1}^{m} (A_{i,k}\times B_{k,j})\)
即 有 \(m \times p\) 的矩阵 \(A\) 设其一行为 \(A_i\) 有 \(p \times n\) 的矩阵 \(B\) 设其一列为 \(B_i\) 则有
\(m \times n\) 的矩阵 \(C=AB\)
其中 \(A_iB_j=\sum\limits_{k=1}^{p} a_{k}b_{k}矩阵\)
这里用了矩阵分块法....矩阵是可以划分成子矩阵形成的矩阵的
性质
加减
- \(A+B=B+A\)
- \((A+B)+C=A+(B+C)\)
- \(A+(-A)=O\)
- \(A-B=A+(-B)\)
数乘
- \((\lambda \mu) A = \lambda (\mu A)\)
- \(\lambda A+\lambda B=\lambda(A+B)\)
- \(\lambda(A+B)=\lambda A+\lambda B\)
乘
- \((AB)C=A(BC)\)
- \(\lambda (AB)=(\lambda A)B=A (\lambda B)\) 其中 \(\lambda\) 为数
- \(A(B+C)=AB+AC,(B+C)A=BA+CA\)
- \(EA=AE=A\)
- \(A^kA^l=A^{k+l}\) \((A^k)^l=A^{kl}\)
矩阵乘法不满足交换律 有 \(AB\) 不一定等于 \(BA\)
转置矩阵
对于一个矩阵 \(A\) 交换其行与列,记为 \(A^T\)
即有
性质
- \((A^T)^T=A\)
- \((A+B)^T=A^T+B^T\)
- \((\lambda A)^T=\lambda A^T\)
- \((AB)^T=A^TB^T\)
单位矩阵
主对角线为 \(1\) 其余为 \(0\) 的矩阵 是一个 \(n\times n\) 的方阵
可记为 \(I\) \(E\) \(U\)
有 \(I_n=\mathrm{diag}(1,1,...,1)\)
余子式
设一个 \(n \times m\) 的矩阵 \(A\)
则 \(A\) 的 \((i,j)\) 余子式 \(M_{ij}\) 为 \(A\) 去掉第 \(i\) 行 第 \(j\) 列后得到的 \(n-1\) 阶子矩阵的行列式
代数余子式
一个矩阵 \(A\) 的代数余子式 \(C_{ij}=(-1)^{i+j}M_{ij}\)
伴随矩阵
将行列式 \(|A|\) 的各元素的代数余子式 \(A_{ij}\) 构成以下矩阵
则 \(A^*\) 为 \(A\) 的伴随矩阵
有 \(AA*=A^*A=|A|E\)
逆矩阵
对于 \(n\) 阶矩阵 \(A\) 如果有一个 \(n\) 阶矩阵 \(B\) ,使 \(AB=BA=E\)
那么称矩阵 \(A\) 是可逆的,称 \(B\) 为 \(A\) 的逆矩阵,记为 \(A^{-1}\)
性质
- 若 \(A\) 可逆,则其逆矩阵唯一
- 若 \(A\) 可逆,则 \(|A| \ne 0\)
- 若 \(|A|\ne 0\) 则矩阵 \(A\) 可逆,且 \(A^{-1}=\dfrac{1}{|A|}A^*\)
- 若 \(AB=E\) 或 \(BA=E\) 则 \(B=A^{-1}\)
运算规律
- 若 \(A\) 可逆,则 \(A^{-1}\) 也可逆,且 \((A^{-1})^{-1}=A\)
- 若 \(A\) 可逆,数 \(\lambda \ne 0\) ,则 \(\lambda A\) 可逆,且 \((\lambda A)^{-1}=\dfrac{1}{\lambda} A^{-1}\)
- 若 $A,B $ 为同阶矩阵且均可逆,则 \(AB\) 也可逆,且 \((AB)^{-1}=B^{-1}A^{-1}\)
线性方程组
线性方程组为以下形式
可记为一个向量方程 \(Ax=b\)
其中 有
线性变换
对于一个变换 \(A\) 取两个向量,满足可加性和齐次性 有
则可以用矩阵描述这种变换
亦即 \(f(x)=A_fx\)
为 \(\R^n\) 射到 \(\R^m\) 的线性变换构成的向量空间 \(\mathcal{L}(\R^n,\R^m)\) 上存在的一个到 \(\mathcal{M}(m,n,\R)\) 的一一映射: \(f \mapsto A_f\)
图论
可以用一个矩阵 \(E\) 描述一个有限图, \(E\) 为相关矩阵的邻接矩阵,记录了图两顶点之间是否有边链接.对一个简单图而言,其元素取值为 \(0/1\)
一个距离矩阵 \(E\) 各元素为点之间距离的矩阵
构造(转移)矩阵
考虑一种特殊的情况
设 \(F\) 是一个 \(1\times n\) 的矩阵(也就是数列) \(A\) 是 \(n \times n\) 的(转移)矩阵
有 \(F,F'\) 为一维数组 略去其行下标
对于 \(\forall j \in [1,n]\) 有 \(F'_j=\sum\limits_{k=1}^{n}(F_k \times A_{k,j})\)
等价于在一个向量的 \(k,j\) 两个状态之间发生了转移
这样 可以用 状态矩阵 \(F\) 与一个 转移矩阵 \(A\) 来表示数列的线性递推关系
同时 可以用矩阵快速幂优化这个过程 总复杂度 \(O(n^3 \log T)\)
OI会用到的
矩阵加速递推
矩阵表达修改
定长路径统计
定长最短路
限长路径计数/最短路
struct Matrix //这份矩阵的正确性无法保证
{
int e[MAX][MAX],n,m;
Matrix I(int n);
Matrix(int N=0,int M=0):n(N),m(M),e(){}
Matrix operator+(const Matrix &x)const ;
Matrix operator*(const Matrix &x)const ;
};
Matrix Matrix::I(int n)
{
Matrix t=Matrix(n,n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) t.e[i][j]=1;
return t;
}
Matrix Matrix::operator*(const Matrix &x)const
{
Matrix t=Matrix(n,x.m);
for(int k=1;k<=m;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=x.m;j++) t.e[i][j]+=e[i][k]*x.e[k][j];
return t;
}
Matrix Matrix::operator+(const Matrix &x)const
{
Matrix t=Matrix(n,m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) t.e[i][j]=e[i][j]+x.e[i][j];
return t;
}
Matrix qpow(Matrix x,int k)
{
Matrix e=e.I(x.n),t(x);
while(k)
{
if(k&1) e=e*t;
t=t*t; k>>=1;
}
return e;
}
行列式
在数学中,行列式是一个函数,其定义域为 \(det\) 的矩阵 \(A\),取值为一个标量,写作 \(det(A)\) 或 \(|A|\)
逆序
对于 \(n\) 个不同元素,有一个标准次序 \(1,2,...,n\) 其下标为 \(p_1,p_2,...,p_n\)
一个逆序对 即 \(p_i>p_j\) 且 \(i<j\) 的有序数对
一个序列的逆序即其逆序对总数
计算
设有 \(n^2\) 个数,排成 \(n \times n\) 的数表
令 \(t\) 为列下标排列 \(p_1p_2...p_n\) 的逆序数
则 \(D=\sum (-1)^t a_{1 p_1} a_{2p_2}\cdots a_{n p_n}\)
性质
...直接爆算
意义
从几何上来看 行列式 \(R^n\) 是 \(n\) 维空间下几何的体积
组合计数
序列定义
串
有限集 \(S\) 上的串是集合 \(S\) 中元素构成的一个序列
串 \(s\) 的字串 \(s'\) 是 \(s\) 中连续若干个元素的有序序列
排列
有限集 \(S\) 的一个排列是 \(S\) 中元素的一个有序序列,其中每个元素出现且仅出现一次
组合
\(n\) 集合 \(S\) 的一个 \(k\) 组合是 \(S\) 的一个 \(k\) 子集
加法原理
若完成一件事的方法有 \(n\) 类,其中第 \(i\) 类方法包括 \(a_i\) 种不同的方法,且这些方法互不重和
则完成这件事共有 \(a_1+a_2+...+a_n\) 种不同的方法
乘法原理
若完成一件事需要 \(n\) 个步骤,其中第 \(i\) 步骤有 \(a_i\) 种不同的完成方法,且这些步骤互不干扰
则完成这件事共有 \(a_1\times a_2 \times ... \times a_n\) 种不同的方法
排列数
从 \(n\) 个不同元素一次取出 \(m\) 个元素排成一列,产生的不同排列的数量(记为 \(P\) )
则 \(P_n^m=\dfrac{n!}{(n-m)!}\)
多重集合的排列数
设 \(S=\{n_1\cdot a_1,n_2 \cdot a_2 ,...,n_k \cdot a_k \}\) 是一个多重集,则 \(S\) 的全排列个数为 \(\dfrac{n!}{n_1!n_2!...n_k!}\)
多重集合的组合数
设 \(S=\{n_1\cdot a_1,n_2 \cdot a_2 ,...,n_k \cdot a_k \}\) 是一个多重集,则从 \(S\) 中取 \(r\) 个组成的多重集(不考虑元素顺序)数量为 \(C_{n+r-1}^{k-1}\)
组合数
从 \(n\) 个不同元素依次取出 \(m\) 个元素组成与一个集合(不考虑顺序),产生的不同集合的数量(记为 \(C_{n}^{r}\) 也记为 \(\binom{n}{r}\))
则 \(C_n^m=\dfrac{n!}{m!(n-m)!}\)
性质
-
\(C_n^m=C_n^{n-m}\)
-
\(C_n^m=C_{n-1}^m+C_{n-1}^{m-1}\)
-
\(C_n^0+C_n^1+...+C_n^n=2^n\)
二项式定理
有 \((a+b)^n=\sum\limits_{k=0}^n C_n^ka^kb^{n-k}\)
Lucas 定理
若 \(p\) 是质数,则对于任意的 \(1\le m \le n\) 有
有
int C(int n,int m); //计算组合数
//
int Lucas(int n,int m,int p)
{
if(m==0) return 1;
return (C(n%p,m%p)*Lucas(n/p,m/p,p)%p)%p;
}
莫比乌斯变换
定义一个函数 \(F(n)=\sum\limits_{d|n} f(d)\)
对于函数 \(f(n)\) 较难求解 但可以很容易求解其 倍数和 或 约数和 \(F(n)\) , 反推 \(f(n)\) 的过程
引理 1
引理 2
数论分块
用于求解形如 \(\sum\limits_{i=1}^{n}f(i)\) 的式子
如果有一定循环节 使得 \(x,y\in [i,j]\) 有 \(f(x)=f(y)\) 那么可以用一段区间计算函数值
- 有形如 \(f(i)=\lfloor \frac{n}{i} \rfloor\)
那么 对于一个区间 \(l\) 有右端点 \(\lfloor \dfrac{n}{\lfloor \frac{n}{l} \rfloor} \rfloor\) 即可在 \(O(\sqrt n)\) 的时间求解
for(int l=1,r;l<=n;l=r+1)
{
r=n/(n/l);
_solve();
}
- 有形如 \(f(i)=(k \mod i)\)
可转化为 \(\sum\limits_{i=1}^{n} (k-i\lfloor \frac{k}{i} \rfloor)\) 求解
- 有求 \(\sum\limits_{i=1}^{min(n,m)} \lfloor \frac{n}{i} \rfloor\lfloor \frac{m}{i} \rfloor\)
for(int l=1,r;l<=n;l=r+1)
{
r=min(n/(n/l),m/(m/l));
_solve();
}
积性函数
积性函数
若 \(f(n)\) 满足 \(f(1)=1\) 且 \(\forall x,y \in D,gcd(x,y)=1\) 都有 \(f(xy)=f(x)f(y)\)
这里和下面的 \(D\) 是整数集合 一般有 \(D=\N_+\)
完全积性函数
若 \(f(n)\) 满足 \(f(1)=1\) 且 \(\forall x,y \in D\) 都有 \(f(xy)=f(x)f(y)\)
性质
若 \(f(x)\) \(g(x)\) 为积性函数 则以下也为积性函数
- \(h(x)=f(x^p)\)
- \(h(x)=f^p(x)\)
- \(h(x)=f(x)g(x)\)
- \(h(x)=\sum\limits_{d \mid x} f(d) g(\frac{x}{d})\)
设 \(x= \prod p_i^{k_i}\)
若 \(F(x)\) 为积性函数 则有 \(F(x)=\prod F(p_i^{k_i})\)
若 \(F(x)\) 为完全积性函数 则有 \(F(X)=\prod F(p_i^{k_i})\)
Dirichlet 卷积
定义两个数数论函数的 \(f,g\) 的 Dirichlet 卷积为 \((f*g)(n)=\sum\limits_{d|n}f(d)g(\frac{n}{d})\)
性质
- 交换律 \(f*g=g*f\)
- 结合律 \((f*g)*h=f*(h*g)\)
- 分配率 \(f*(g+h)=f*g+f*h\)
- \(f*\varepsilon=f\) 其中 \(\varepsilon\) 为单位元
莫比乌斯函数
\(\mu\) 为莫比乌斯函数 定义为
性质与结论
-
莫比乌斯函数为积性函数
-
\[\sum\limits_{d|n} \mu(d)= \left\{\begin{aligned} 1 \qquad n=1 \\ 0 \qquad n \ne 1 \end{aligned} \right. \]
-
\([\gcd(i,j)=1] \iff \sum\limits_{d|\gcd(i,j)} \mu(d)d\)
莫比乌斯反演
设 \(g(n)\) \(f(n)\) 为两个数论函数
如有 \(f(n)=\sum\limits_{d|n}g(d)\) 则有 \(g(n)=\sum\limits_{d|n}\mu(d)f(\frac{n}{d})\)
如有 \(f(n)=\sum\limits_{n|d}g(d)\) 则有 \(g(n)=\sum\limits_{n|d}\mu(\frac{d}{n})f(d)\)
容斥原理
容斥原理
设 \(U\) 中元素有 \(n\) 种不同的属性 , 而第 \(i\) 种属性为 $P_i $ , 有 \(P_i\) 属性的为 \(S_i\) 则
特别的
当仅有两个集合 有 \(|A \cup B|=|A|+|B|-|A \cap B|\)
当仅有三个集合 有 \(|A \cup B \cup C|=|A|+|B|+|C|-|A\cap B|-|B\cap C| - |A \cap C| +|A \cap B \cap C|\)
鸽巢原理
若 \(A\) 为 \(n+1\) 元集 \(B\) 为 \(n\) 元集 则不存在 \(A\) 到 \(B\) 的单射
若要将 \(n\) 对象分配到 \(m\) 个容器 必有一个容器容纳至少 $\lceil\frac{n}{m} \rceil $ 个对象
Catalan 数
一个定义:给定 \(n\) 个 \(0\) 和 \(n\) 个 \(1\) ,它们按某种顺序排成的长度长为 \(2n\) 的序列,满足任意前缀 \(0\) 的个数都不少于 \(1\) 的个数的序列的数量为 \(H_n=\dfrac{\binom{2n}n}{n+1}\)
也有
\(H_n=\dfrac{H_{n-1}(4n-2)}{n+1}\)
\(H_n= \binom{2n}{n}-\binom{2n}{n-1}\)
概率与期望
定义
单位(基本)事件
在一次随机试验 \(E\) 中可能发生的不能再细分的结果
事件(样本)空间
在随机试验中可能发生的所有单位事件的集合 记为 \(S\) 其元素为基本事件,其一个子集为事件,根据定义,所有基本事件互斥
概率
设 \(E\) 是随机试验, \(S\) 是它的样本空间.对 \(E\) 的每一个事件 \(A\) 赋予一个实数,记为 \(P(A)\) ,称为事件 \(A\) 的概率.这里 \(P(A)\) 是一个从集合到实数的映射,满足以下公理
- 非负性 对于一个事件 \(A\) 有概率 \(P(A) \ge 0\)
- 规范性 事件空间的概率为 \(1\) 即 \(P(S)=1\)
- 可列可加性 若 $A \cap B\cap \cdots = \varnothing $ 则 \(P(A\cup B\cup \cdots)=P(A)+P(B)+\cdots\)
有 \((S,P)\) 构成的一个系统称为一个 概率空间
等可能概形(古典概型)
- 样本空间仅包含有限个元素
- 每个基本事件发生的可能性相同
若事件 \(A\) 包含 \(k\) 个基本事件即 \(A=\{e_{i_1}\} \cup ...\cup \{e_{1_k}\}\)
\(P(A)=\sum\limits_{j=1}^{k}P(\{e_{i_j}\})=\dfrac{k}{n}=\dfrac{A 中包含的基本事件总数}{S 中包含的基本事件总数}\)
离散型随机变量
取值范围为有限或无限可数个实数的随机变量
连续型随机变量
如果 \(X\) 是在实数域或区间上取连续值的随机变量,设 \(X\) 的概率分布函数是 \(F(x)=P(X\le x)\) ,若存在非负可积函数 \(f(x)\) ,使对任意的 \(x\) ,有 \(F(x)=\int_{-\infty}^{x}f(t)\mathrm{d}t\) ,则称 \(X\) 为连续型随机变量,称 \(f(x)\) 为 \(X\) 的概率密度函数
计算
-
广义加法公式 对于任意事件 \(A,B\) 有 \(P(A \cup B)=P(A)+P(B)-P(A \cap B)\)
-
条件概率 记 \(P(B|A)\) 为 \(A\) 发生的前提下 \(B\) 发生的概率 则 \(P(A|B)=\dfrac{P(AB)}{P(A)}\) 其中 \(P(AB)\) 为事件 \(A\) 和事件 \(B\) 同时发生的概率
-
乘法公式 \(P(AB)=P(A) \cdot P(B|A)=P(B)\cdot P(A|B)\)
-
若事件 \(A_1,A_2, ... ,A_n\) 构成一组完备的事件且都有正概率 即 \(\forall i,j,A_i \cap A_j = \varnothing\) 且 \(\sum\limits_{i=1}^{n} A_i =1\) 则有 \(P(B)=\sum\limits_{i=1}^{n} P(A_i)P(B|A_i)\)
-
贝叶斯定理 \(P(B_i|A)=\dfrac{P(B_i)P(A|B_i)}{\sum\limits_{j=1}^{n}P(B_j)P(A|B_j)}\)
独立性
随机事件的独立性
称两个事件 \(A,B\) 独立 当 \(P(A \cap B)=P(A)P(B)\)
称若干个事件 \(P(\bigcap\limits_{E \in T})=\prod\limits_{E \in T}p(E),\forall T \subseteq \{A_1,...,A_n\}\)
随机变量的独立性
以下用 \(I(X)\) 表示随机变量 \(X\) 的取值范围.即,如果把 \(X\) 看作一个映射,则 \(I(X)\) 就是其值域
称两个随机变量 \(X,Y\) 独立 ,当 \(P((X=\alpha)\cap(Y=\beta))=P(X=\alpha)P(Y=\beta),\forall \alpha \in I(X),\beta \in I(Y)\) 即 \((X,Y)\) 取任意一组值的概率,等于 \(X\) 和 \(Y\) 分别取对应值的概率乘积
我们称若干个随机变量 \(P(\bigcap\limits_{i=1}^{n}X_i=F_i\prod\limits_{i=1}^{n},\forall F_{1...n}\)
期望
如果一个随机变量的取值个数有限(比如一个表示骰子示数的随机变量),或可能的取值可以一一列举出来(比如取值范围为全体正整数),则它称为 离散型随机变量
形式化地说,一个随机变量被称为离散型随机变量,当它的值域大小 有限 或者为 可列无穷大
一个离散性随机变量 \(X\) 的 数学期望 是其每个取值乘以该取值对应概率的总和,记为 \(E(X)\)
有 \(I(X)\) 表随机变量 \(X\) 的值域, \(S\) 表 \(X\) 所在的样本空间的集合
也称均值 有
离散型 \(E(X)=\sum\limits_{k=1}^{\infty}=x_kp_k\)
连续型 \(E(X)=\int_{-\infty}^\infty xf(x)\mathrm{d} x\)
性质
- 全期望公式 \(E(Y)=\sum\limits_{\alpha \in I(X)}P(X=\alpha)E(Y|(X=\alpha))\) 其中 \(X,Y\) 是随机变量, \(E(Y|A)\) 是 \(A\) 成立条件下 \(Y\) 的期望(条件期望)
- 期望的线性 对于两个随机变量 \(X,Y\) 有 \(E(aX+bY)=aE(X)+bE(Y)\)
- 乘积的期望 当两个随机变量 \(X,Y\) 互相独立 有 \(E(XY)=E(X)E(Y)\)
差分约束
给出 \(n\) 个变量和 \(m\) 个约束条件
形如 \(x_i-x_j\le c_k\)
求解 \(x_i\) 的值
假设形如 \(x_i-x_j \ge c_k\) 则两边同乘 \(-1\)
假如形如 \(x_i-x_j=c_k\) 那么拆成 \(x_i-x_j \le c_k\) 和 \(x_i-x_j \ge c_k\)
那么仅讨论 \(x_i-x_j \le c_k\)
变形一下 得 \(x_i \le x_j + c_k\) 发现跟 \(dis_v \le dis_u + w\) 很类似
图论建图
有 \(0\) 号点到其它所有点连一条长为 \(0\) 的边 相当于一个约束 \(x_i \le x_0\)
将其它约束 \(x_i -x_ j \ge c_k\) 连一条 \(j \to i\) 长为 \(c_k\) 的边
从 \(0\) 号点开始跑 \(\mathrm{SPFA}\)
如有负环 无解
否则 \(x_i=dis_i\) 为一组可行解
字符串
Trie
struct trie{
int next[MAX][26],cnt;
bool exist[MAX];
void insert(char *s,int len)
{
int p=0;
for(int i=0;i<len;i++)
{
int c=s[i]-'a';
if(!next[p][c]) next[p][c]=++cnt;
p=next[p][c];
}
exist[p]=1;
}
bool find(char *s,int len)
{
int p=0;
for(int i=1;i<len;i++)
{
int c=s[i]-'a';
if(!next[p][c]) return 0;
p=next[p][c];
}
return exist[p];
}
};
KMP
假设一篇英文文章 以 \(26\) 个字母和空格随机组成 那么暴力匹配 \(k\) 位的概率是 \((\frac{1}{27})^k\) 大误
char a[MAX],b[MAX];
int lena,lenb,nxt[MAX];
stack<int> pos; //存匹配到的位置
//a,b 下标从1开始
//
void get_next()
{
for(int i=2,j=0;i<=lenb;i++)
{
while(j&&b[i]!=b[j+1]) j=nxt[j];
if(b[j+1]==b[i]) j++;
nxt[i]=j;
}
}
void kmp()
{
for(int i=1,j=0;i<=lena;i++)
{
while(j&&b[j+1]!=a[i]) j=nxt[j];
if(b[j+1]==a[i]) j++;
if(j==lenb)
{
pos.push(i-lenb+1);
j=nxt[j];
}
}
}
AC自动机
char s[151][N],T[N];
int n,cnt,vis[N],ans;
struct trie{int to[26],fail,end;}t[N];
queue<int> q;
void query(char* s);
void getfail();
void insert(char* s,int num);
void init();
int main()
{
while(1)
{
scanf("%d",&n);
if(!n) break;
init();
for(int i=1;i<=n;i++) scanf("%s",s[i]),insert(s[i],i);
scanf("%s",T);
getfail();
query(T);
for(int i=1;i<=n;i++) ans=max(vis[i],ans);
printf("%d\n",ans);
for(int i=1;i<=n;i++)
if(vis[i]==ans) printf("%s\n",s[i]);
}
}
void insert(char* s,int num)
{
int u=1,len=strlen(s);
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
if(!t[u].to[v]) t[u].to[v]=++cnt;
u=t[u].to[v];
}
t[u].end=num;
}
void getfail()
{
for(int i=0;i<26;i++) t[0].to[i]=1;
q.push(1); t[1].fail=0;
while(!q.empty())
{
int u=q.front(); q.pop();
int F=t[u].fail;
for(int i=0;i<26;i++)
{
int v=t[u].to[i];
if(!v)
{
t[u].to[i]=t[F].to[i];
continue;
}
t[v].fail=t[F].to[i];
q.push(v);
}
}
}
void query(char* s)
{
int u=1,len=strlen(s);
for(int i=0;i<len;i++)
{
int v=s[i]-'a';
int k=t[u].to[v];
while(k>1)
{
if(t[k].end) vis[t[k].end]++;
k=t[k].fail;
}
u=t[u].to[v];
}
}
void init()
{
memset(t,0,sizeof(t));
memset(vis,0,sizeof(vis));
cnt=1; ans=0;
}
int getlen(char *s)
{
int len=0;
while(s[len+1]) len++;
return len;
}
后缀排序
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1000500
using namespace std;
char s[N];
int n,m,x[N],y[N],bucket[N],sa[N];
void SA();
int main()
{
scanf("%s",s+1);
n=strlen(s+1); m=(int)'z';/*122*/
SA();
for(int i=1;i<=n;i++) cout<<sa[i]<<" ";
return 0;
}
void SA()
{
for(int i=1;i<=n;i++) bucket[x[i]=s[i]]++;
for(int i=2;i<=m;i++) bucket[i]+=bucket[i-1];
for(int i=n;i>=1;i--) sa[bucket[x[i]]--]=i;
for(int k=1;k<=n;k<<=1)
{
int num=0;
for(int i=n-k+1;i<=n;i++) y[++num]=i;
for(int i=1;i<=n;i++)
if(sa[i]>k) y[++num]=sa[i]-k;
for(int i=1;i<=m;i++) bucket[i]=0;
for(int i=1;i<=n;i++) bucket[x[i]]++;
for(int i=2;i<=m;i++) bucket[i]+=bucket[i-1];
for(int i=n;i>=1;i--) sa[bucket[x[y[i]]]--]=y[i],y[i]=0;
swap(x,y);
x[sa[1]]=1; num=1;
for(int i=2;i<=n;i++)
if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k]) x[sa[i]]=num;
else x[sa[i]]=++num;
if(num==n) break;
m=num;
}
}
搜索
DFS
int dfs(_state())
{
if(_end()) return _update(_ans());
_change(_state());
_solve(_state());
dfs(_anotherState());
_restore(_state());
}
BFS
int bfs()
{
queue<_stateDATA> q;
q.push(_startState()); _set(check[_startState()],1);
while(!q.empty())
{
_stateDATA now=q.front(); q.pop();
if(check[now]) continue;
check[now]=1;
_solve(now);
q.push(_anotherState());
}
return _ans();
}
A*
有 开始到目前节点的代价 \(g(x)\) 当前节点到终点的代价 \(h(x)\) 令 \(f(x)=g(x)+h(x)\)
如果 \(h(x)\le h^*(x)\) A* 一定能找到最优解
当 \(h=0\) \(A^*\)变为 DFS 当 \(h=0\) 且边权为 \(1\) \(A^*\) 变为 BFS
int bfs()
{
priority_queue<_stateDATA(_cmpf())> q; //greater_priority_queue
q.push(_startState(),f(_startState()));
while(!q.empty())
{
_stateDATA now=q.top(); q.pop();
_solve(now);
if(_ans()==_need()) return _ans();
q.push(_anotherState(),f(_anotherState()));
}
return _ans();
}
int f(int x)
{
return _g(x)+_h(x);
}
迭代加深
int dfs(_state(),deep)
{
if(deep>_limit()) return _ans();
if(_end()) return _ans();
_change(_state());
_solve(_state());
dfs(_anotherState(),deep+1);
_restore(_state());
}
IDA*
int dfs(int deep)
{
int h=_h();
if(h==0) return _solved();
if(deep+h>limit) return deep+h;
_change(_state());
_solve(_state());
dfs(_anotherState(),deep+1);
_restore(_state());
}
Meet in the middle
将搜索过程分成两半分别搜索 最后合并
往往复杂度优化是 \(O(a^b)\to O(a^\frac{b}{2}+合并时间复杂度)\)
双向搜索
从起点和终点同时搜索
如果相遇 那么可以认为出现了可行解
queue<_state> qS,qE;
qS.push(_start); qE.push(_end);
//
int solve()
{
while((!qS.empty())||(!qE=empty()))
{
_state u=q.front(); q.pop();
if(_vis(u)) return _val();
_bfs(qS); _bfs(qE); //扩展一次
}
}
分治
分治
int function(int l,int r)
{
if(_solveEasily()) return _solve();
mergeFonction(fonction(l,mid),function(mid+1,r));
}
cdq分治
将一个区间 \([l,r]\) 分成 \([l,mid],[mid+1,r]\) 求解左区间对右区间的贡献
重要的是将问题的规模减半
三维偏序
有 \(n\) 个元素 每个有属性 \((a_i,b_i,c_i)\) 设 \(f(i)\) 表示满足 \(a_j \le a_i 且 b_j \le b_i 且c_j \le c_i 且 j \ne i\) 的 \(j\) 的数量
对于 \(d \in [0,n)\) 求 \(f(i)=d\) 的数量
可以对第一维排序 第二维用树状数组 第三维用 cdq分治
#include<iostream>
#include<cstring>
#include<algorithm>
const int N=200050;
using namespace std;
int n,k,idx,cnt,tot[N];
struct node{int a,b,c,w,ans;}x[N],y[N];
struct Bittree{int t[N],n;
void init();
int lowbit(int x);
int query(int x);
void add(int x,int k);
}t;
bool cmp2(node x,node y);
bool cmp1(node x,node y);
void cdq(int l,int r);
int main()
{
cin>>n>>k;
t.init();
t.n=k;
for(int i=1;i<=n;i++) cin>>y[i].a>>y[i].b>>y[i].c;
sort(y+1,y+n+1,cmp1);
for(int i=1;i<=n;i++)
{
idx++;
if(y[i].a!=y[i+1].a||y[i].b!=y[i+1].b||y[i].c!=y[i+1].c)
x[++cnt]=y[i],x[cnt].w=idx,idx=0;
}
cdq(1,cnt);
for(int i=1;i<=cnt;i++) tot[x[i].ans+x[i].w-1]+=x[i].w;
for(int i=0;i<n;i++) cout<<tot[i]<<endl;
return 0;
}
void Bittree::init()
{
memset(t,0,sizeof(t));
}
int Bittree::lowbit(int x)
{
return x&(-x);
}
int Bittree::query(int x)
{
int re=0;
while(x) re+=t[x],x-=lowbit(x);
return re;
}
void Bittree::add(int x,int k)
{
while(x<=n) t[x]+=k,x+=lowbit(x);
}
void cdq(int l,int r)
{
if(l==r) return ;
int mid=(l+r)>>1;
cdq(l,mid); cdq(mid+1,r);
sort(x+l,x+mid+1,cmp2); sort(x+mid+1,x+r+1,cmp2);
int i=mid+1,j=l;
while(i<=r)
{
while(x[j].b<=x[i].b&&j<=mid) t.add(x[j].c,x[j].w),j++;
x[i].ans+=t.query(x[i].c);
i++;
}
for(i=l;i<j;i++) t.add(x[i].c,-x[i].w);
}
bool cmp2(node x,node y)
{
if(x.b==y.b) return x.c<y.c;
return x.b<y.b;
}
bool cmp1(node x,node y)
{
if(x.a==y.a)
{
if(x.b==y.b) return x.c<y.c;
else return x.b<y.b;
}
return x.a<y.a;
}
暴力
贪心
可以参考思路
- 考虑极端情况(直接取最小值最大值)
- 考虑相邻两个数据交换后的更优解
- 考虑相等情况更优解(是否满足 自反性,反对称性,转递性 等特殊情况)(可以引入第三个数据)
- 如果有左右相等 可直接删除 对答案无影响
- 化简贪心式子(数学)
卡时
clock_t sTime=clock();
while((double)(clock()-sTime)<=LIMIT_TIME) solve();
枚举
顺序
void solve()
{
int A=_init();
_sort(A);
do{
_solve(A);
}while(next_permutation(A)); //倒序prev_permutation(A)
}
划分
for(int S=0;S<(1<<MAX);S++)
{
_solve(S);
}
随机化乱搞
while(_haveTime())
{
random_shuffle(A.begin(),A.end());
_check(A);
}
随机化
Sherwood(舍伍德算法)
通过随机化 消除最坏的结果(消除于实例的关系) 使期望时间复杂度趋于平均值
一定可以跑出正确结果 并可以优化复杂度
\(f(x)\) 是解决某个实例的所需时间,\(X_n\) 为实例的集合
有 \(\overline{f(x)}=\dfrac{\sum\limits_{x \in X_n} f(x)}{n}\)
期望 对于 \(\forall x \in X_n\) 有 \(f(x) \to \overline{f(x)}\)
Las Vegas(拉斯维加斯算法)
随机选择 总能给出正确的结果 但是 也有可能找不到解 优化时间复杂度 好像和上面的差不多
随着运行时间的增加 越有机会找到正解
Monte Carlo(蒙特卡罗算法)
一种结合概率统计的算法 非确定性算法
随着运行时间增加 给出正解的概率更高(越近似正解)
正确性 大数定理 样本数量越多,其平均就越趋近于真实值.
模拟退火
其实就是一种蒙特卡罗算法
正确性玄学 大几率跑出正解
多峰函数求极值
定义当前温度 \(T\) ,新状态与已知状态之间的差 \(\Delta E(\Delta E \ge 0)\) 则发生状态转移(更改最优解(接受当前状态))的概率为
开始时 \(T=T_0\) 有降温系数 \(d\) 终止温度 \(T_k\)
一般的 有 \(d \to 1\) \(T_k \to 0\)
每次转移 让 \(T=T\times d\) 直到 \(T<T_k\)
板子
void SA()
{
double t=_originalT();
_data _state();
while(t>_endT())
{
_date _newState();
double now=solve(_newState());
double delta=now-ans;
if(_canAccept(now)) _accept(_newState());
else if(exp(-delta/t)*RAND_MAX>rand()) _accept(_newState());
t=t*_delta();
}
}
二分
int search(int L,int R)
{
int l=L,r=R,ans;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
bool check(int mid) {return _check();}
三分
const double esp=1e-6;
//
double search(double L,double R)
{
double l=L,r=R;
while(r-l>=esp)
{
double t=(r-l)/3;
if(solve(l+t)>solve(r-t)) r-=t;
else l+=r;
}
return t;
}
double solve(double x) {return _solve();}
整体二分
需要以下性质
- 询问答案有可二分性
- 修改对判定答案的贡献相互独立,修改之间不影响效果
- 修改如果对判定答案有贡献,则必为一确定的与判定标准无关的值
- 贡献满足交换律结合律,有可加性
- 题目允许使用离线算法
莫队
普通莫队
假设 \(n=m\) 那么如对于序列上的区间询问问题 如果从答案 \([l,r]\) 可以 \(O(1)\) 扩展到 \([l-1,r],[l+1,r],[l,r-1],[l,r+1]\) 的答案,那么可以在 \(O(n \sqrt n)\) 内求解所有的答案
模板
void move(int pos,int sign)
{
_updateAns();
}
void solve()
{
size=sqrt(n);
sort(querys+1,querys+m+1);
for(int i=1;i<=m;i++)
{
query q=querys[i];
while(l>q.l) move(--l,1);
while(r<q.r) move(r++,1);
while(l<q.l) move(r++,-1);
while(r>q.r) move(--r,-1);
ans[q.id]=_nowAns;
}
}
文件IO
freopen
freopen("xxx.in","r",stdin);
freopen("xxx.out","w",stdout);
//
//
fclose(stdin);
fclose(stdout);
fstream
ifstream fin("xxx.in");
ofstream fout("xxx.out");
//
//
fin.close();
fout.close();
考试相关
emacs
(setq c-basic-offset 4) ;;4格缩进
(setq default-cursor-type 'bar) ;;将光标改成竖线
(global-linum-mode t) ;;显示行号
kill
ps -aux|grep &NAME
sudo kill &UID
\(\mathrm{NOI} \ \mathrm{Linux}\) 密码大概是 \(123546\)
对拍
while true; do
./data_maker>data.in
./yout_programe<data.in>data.out
./std<data.in>data_check.out
if diff data.out data_check.out; then
printf "AC\n"
else
printf "WA\n"
exit 0
fi
done
运行
cd ./Desktop
sh ./test.sh

浙公网安备 33010602011771号