数据结构
栈
记录先进后出的信息,可用于拓扑排序,Tarjan,单调栈优化,Splay中的Pushdown记录节点……
Code
void Work(){
sta[++top]=x;
x=sta[top--];
}
队列
可记录先进先出的信息,用于单调队列优化,广搜等。
Code
void Work(){
queue <int> q;
q.push(x);
x=q.front();
q.pop();
}
堆
父亲的值优于子树任意一个儿子,可动态维护整个序列中的最大最小值。
Code
struct QNode{
int x;
bool operator < (const QNode &T)const{
return x<T.x;
}
};
void Work(){
priority_queue <QNode> q;
q.push(QNode(x));
T=q.top();
q.pop();
}
可并堆(左偏树)
满足堆式存储,并且可以快速合并。
Code
struct Leftist_Tree{
int lc,rc;
int val,npl;
}tr[maxn];
void init(){ tr[0].npl=-1; }
int Merge(int a,int b){
if(!a) return b;
if(!b) return a;
if(tr[a].val<tr[b].val) swap(a,b);
Pushdown(a);
tr[a].rc=Merge(tr[a].rc,b);
if(tr[tr[a].lc].npl<tr[tr[a].rc].npl) swap(tr[a].lc,tr[a].rc);
tr[a].npl=tr[tr[a].lc].npl+1;
return a;
}
树状数组
利用位运算动态维护前缀和,可记录一些满足区间加减法的信息。
Code
int bit[maxn];
void add(int x,int val){
for(int i=x;i<=n;i+=i&-i)
bit[i]+=val;
}
int query(int x){
int res=0;
for(int i=x;i;i-=i&-i)
res+=bit[i];
return res;
}
Splay
有双旋操作的二叉排序树,用于维护序列,维护凸包,维护元素大小关系……
Code
#define maxn 100000+5
#define L(k) tr[tr[k].ch[0]]
#define R(k) tr[tr[k].ch[1]]
struct Splay_Tree{
int ch[2];
int key,mx,rev,tag,fa,sz;
}tr[maxn];
int stack[maxn],a[maxn];
int top,root,cnt;
void Update(int k){
tr[k].sz=L(k).sz+R(k).sz+1;
tr[k].mx=max(L(k).mx,R(k).mx);
tr[k].mx=max(tr[k].mx,tr[k].key);
}
void Pushdown(int k){
if(tr[k].tag){
if(tr[k].ch[0]) L(k).tag+=tr[k].tag,L(k).mx+=tr[k].tag,L(k).key+=tr[k].tag;
if(tr[k].ch[1]) R(k).tag+=tr[k].tag,R(k).mx+=tr[k].tag,R(k).key+=tr[k].tag;
}
if(tr[k].rev){
swap(tr[k].ch[0],tr[k].ch[1]);
if(tr[k].ch[0]) L(k).rev^=1;
if(tr[k].ch[1]) R(k).rev^=1;
}
tr[k].rev=tr[k].tag=0;
}
void Rotate(int &k,int d){
int t=tr[k].ch[d^1];
if(tr[t].ch[d]) tr[tr[t].ch[d]].fa=k;
tr[t].fa=tr[k].fa; tr[k].fa=t;
tr[k].ch[d^1]=tr[t].ch[d]; tr[t].ch[d]=k;
Update(k); Update(t); k=t;
}
void Splay(int &k,int x){
if(tr[k].ch[0]) Pushdown(tr[k].ch[0]);
if(tr[k].ch[1]) Pushdown(tr[k].ch[1]);
int d1=L(k).sz<x?1:0,t=tr[k].ch[d1];
if(!d1) x-=L(k).sz+1;
if(x){
int d2=L(t).sz<x?1:0;
if(!d2) x-=L(t).sz+1;
if(x){
Splay(tr[t].ch[d2],x);
if(d1==d2) Rotate(k,d1^1);
else Rotate(tr[k].ch[d1],d1);
}
Rotate(k,d1^1);
}
}
int rank(int k){
int res=L(k).sz+1;
stack[++top]=k;
while(tr[k].fa){
int p=tr[k].fa;
stack[++top]=p;
if(tr[p].ch[1]==k) res+=L(p).sz+1;
k=p;
}
return res;
}
void Insert(int pos,int val){
Splay(root,pos+1); Splay(tr[root].ch[1],pos+1-L(root).sz);
R(root).ch[0]=++cnt;
tr[cnt].key=tr[cnt].mx=val; tr[cnt].fa=tr[root].ch[1];
tr[cnt].sz=1;
Update(tr[root].ch[1]); Update(root);
}
void Delete(int l,int r){ //有必要需回收垃圾,开个stack存储剩余pos
Splay(root,l); Splay(tr[root].ch[1],r+1-L(root).sz);
R(root).ch[0]=0;
Update(tr[root].ch[1]); Update(root);
}
void Reverse(int l,int r){
Splay(root,l); Splay(tr[root].ch[1],r+1-L(root).sz);
int pos=R(root).ch[0];
if(!tr[pos].rev){
tr[pos].rev^=1;
swap(tr[pos].ch[0],tr[pos].ch[1]);
}
Update(tr[root].ch[1]); Update(root);
}
int Build(int l,int r){
if(l>r) return 0;
if(l==r){
tr[l].key=tr[l].mx=a[l]; tr[l].sz=1;
return l;
}
int k=(l+r)>>1;
tr[k].ch[0]=Build(l,k-1);
tr[k].ch[1]=Build(k+1,r);
Update(k);
}
线段树
每个节点维护一个区间,每次修改最多访问logn个节点,注意lazy标记有两个或两个以上时要分清楚标记的先后顺序,科维护区间信息,维护连通性……
Code
#define maxn 200000+5
typedef long long LL;
struct Seg_Tree{
int l,r;
int mx,tag; LL sum;
}tr[maxn<<2];
int tmp[maxn];
void Update(int k){
tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
tr[k].mx=max(tr[k<<1].mx,tr[k<<1|1].mx);
}
void Pushdown(int k){
int sz1=tr[k<<1].r-tr[k<<1].l+1;
int sz2=tr[k<<1|1].r-tr[k<<1|1].l+1;
if(tr[k].tag){
tr[k<<1].tag+=tr[k].tag; tr[k<<1|1].tag+=tr[k].tag;
tr[k<<1].sum+=(LL)tr[k].tag*sz1; tr[k<<1|1].sum+=(LL)tr[k].tag*sz2;
tr[k<<1].mx+=tr[k].tag; tr[k<<1|1].mx+=tr[k].tag;
tr[k].tag=0;
}
}
void Build(int k,int l,int r){
tr[k].l=l; tr[k].r=r;
if(l==r){
tr[k].mx=tr[k].sum=tmp[l];
return;
}
int mid=(l+r)>>1;
Build(k<<1,l,mid);
Build(k<<1|1,mid+1,r);
Update(k);
}
void Modify(int k,int l,int r,int val){
Pushdown(k);
if(tr[k].l==l && tr[k].r==r){
tr[k].sum+=val*(r-l+1); tr[k].tag+=val; tr[k].mx+=val;
return;
}
if(tr[k<<1].r>=r) Modify(k<<1,l,r,val);
else if(tr[k<<1|1].l<=l) Modify(k<<1|1,l,r,val);
else Modify(k<<1,l,tr[k<<1].r,val),Modify(k<<1|1,tr[k<<1|1].l,r,val);
Update(k);
}
LL Query(int k,int l,int r){
Pushdown(k);
if(tr[k].l==l && tr[k].r==r) return tr[k].sum;
if(tr[k<<1].r>=r) return Query(k<<1,l,r);
else if(tr[k<<1|1].l<=l) return Query(k<<1|1,l,r);
else return Query(k<<1,l,tr[k<<1].r)+Query(k<<1|1,tr[k<<1|1].l,r);
}
树链剖分
void dfs1(int x,int d){
sz[x]=1; dep[x]=d; son[x]=0;
for(int i=V.head[x];i!=-1;i=V.nxt[i]){
dfs1(V.v[i],d+1);
sz[x]+=sz[V.v[i]];
if(sz[V.v[i]]>sz[son[x]]) son[x]=V.v[i];
}
}
void dfs2(int x,int tp){
top[x]=tp; w[x]=++cnt; ord[cnt]=x;
if(son[x]) dfs2(son[x],tp);
for(int i=V.head[x];i!=-1;i=V.nxt[i])
if(V.v[i]!=son[x])
dfs2(V.v[i],V.v[i]);
}
int work(int x,int y){ // x为y父亲,否则需要每次找最深的向上回溯,知道其top相同
int res=0;
for(;top[x]!=top[y];y=fa[top[y]])
res+=Query(1,w[top[y]],w[y],p);
return res;
}
主席树
如果n个节点中相邻两个节点的线段树差别不大时,我们可以考虑对每个节点都记录一个版本的线段树,相邻线段树直接的转移可以通过修改log n个节点得到。
Code
struct Seg_Tree{
int lc,rc;
LL sum;
}tr[maxn<<5];
int root[maxn];
void Insert(int last,int &k,int l,int r,int p,int val){
k=++tot;
tr[k]=tr[last]; tr[k].sum+=val
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) Insert(tr[last].lc,tr[k].lc,l,mid,p,val);
else Insert(tr[last].rc,tr[k].rc,mid+1,r,p,val);
}
LL Query(int k1,int k2,int l,int r,int p){
if(l==r) return tr[k2].sum-tr[k1].sum;
int mid=(l+r)>>1;
if(p<=mid) return Query(tr[k1].lc,tr[k2].lc,l,mid,p);
else return Query(tr[k1].rc,tr[k2].rc,mid+1,r,p)+tr[tr[k2].lc].sum-tr[tr[k1].lc].sum;
}
void Modify(int x,int pos,int val){
for(int i=pos;i<=n;i+=i&-i)
Insert(root[i],root[i],1,n,x,val);
}
Link-Cut-Tree
类似于树链剖分,使用Splay等数据结构动态维护树的结构,支持Link,Cut等操作。
Code
struct Splay_Tree{
int ch[2];
int val,sz,fa,pf,rev;
}tr[maxn];
int stack[maxn],top;
int rank(int k,int &rt){
int res=tr[tr[k].ch[0]].sz+1;
stack[++top]=k;
while(tr[k].fa){
int p=tr[k].fa;
if(tr[p].ch[1]==k) res+=tr[tr[p].ch[0]].sz+1;
stack[++top]=p; k=p;
}
rt=k;
return res;
}
void Haha(int k){
int rt,x=rank(k,rt);
while(top) Pushdown(stack[top--]);
x=rank(k,rt); Splay(rt,x);
}
void Cutright(int k){
if(tr[k].ch[1]){
tr[tr[k].ch[1]].pf=k;
tr[tr[k].ch[1]].fa=0;
tr[k].ch[1]=0;
Update(k);
}
}
void Access(int k){
Haha(k); Cutright(k);
for(int u;u;Update(u),k=u){
u=tr[k].pf;
Haha(u); Cutright(u);
tr[u].ch[1]=k;
tr[k].pf=0; tr[k].fa=u;
}
}
void Beroot(int k){
Access(k); Haha(k);
tr[k].rev^=1;
}
int Find(int k){
Access(k); Haha(k);
while(tr[k].ch[0]) k=tr[k].ch[0];
return k;
}
void Link(int x,int y){
int rt1=Find(x),rt2=Find(y);
if(rt1==rt2) return;
Beroot(x); Access(y);
tr[x].pf=y;
}
void Cut(int x,int y){
Beroot(x); Access(y); Haha(y);
tr[y].ch[0]=tr[x].pf=tr[x].fa=0;
Update(y);
}
int Query(int x,int y){
Beroot(x); Access(y); Haha(y);
return tr[y].val;
}
图论
二分图匹配(匈牙利算法)
Code
int link[maxn],vis[maxn];
bool match(int x){
for(int i=head[x];i!=-1;i=e[i].nxt)
if(!vis[e[i].v]){
vis[e[i].v]=1;
if(link[e[i].v]==-1 || match(link[e[i].v])){
link[e[i].v]=x;
return true;
}
}
return false;
}
二分图带权匹配(KM)
//------------------未完待续1s----------------------
网络流
1. 最大流(Dinic)
BFS分层图标号,DFS增广(当前弧优化记录在当前分层图到过的边)
Code
struct Edge{
int u,v,cap;
int nxt;
}e[maxn<<2];
int lv[maxn],bow[maxn],head[maxn];
int n,m,s,t,ans,ind;
void addedge(int x,int y,int w){
e[ind]=(Edge){x,y,w,head[x]},head[x]=ind++;
e[ind]=(Edge){y,x,0,head[y]},head[y]=ind++;
}
bool bfs(){
queue <int> q;
memset(lv,0,sizeof(int)*(t+1));
lv[s]=1; q.push(s);
while(!q.empty()){
int x=q.front(); q.pop();
for(int i=head[x];i!=-1;i=e[i].nxt)
if(e[i].cap && !lv[e[i].v]){
lv[e[i].v]=lv[x]+1;
q.push(e[i].v);
}
}
return lv[t];
}
int dfs(int x,int t,int f){
if(x==t) return f;
for(int &i=bow[x];i!=-1;i=e[i].nxt)
if(e[i].cap && lv[e[i].v]>lv[x]){
int fff=dfs(e[i].v,t,min(e[i].cap,f));
if(fff){
e[i].cap-=fff;
e[i^1].cap+=fff;
return fff;
}
}
return 0;
}
void dinic(){
while(bfs()){
memcpy(bow,head,sizeof(int)*(t+1));
while(true){
int fff=dfs(s,t,INF);
if(fff) ans+=fff;
else break;
}
}
}
有上下界:转化为费用流,把边拆成两条,一条为(up-low,1),一条为(low,-INF),只需最后加上-INF的值即可。
2.费用流(SPFA)
用SPFA增广费用最小的边,记录沿途信息。
struct Edge{
int u,v,cap,w;
int nxt;
}e[maxn<<2];
int pre[maxn],pre_e[maxn],dis[maxn],inq[maxn],head[maxn];
int s,t,ans,ind;
void addedge(int x,int y,int c,int w){
e[ind]=(Edge){x,y,c, w,head[x]},head[x]=ind++;
e[ind]=(Edge){y,x,0,-w,head[y]},head[y]=ind++;
}
bool SPFA(){
queue <int> q;
memset(dis,0x3f,sizeof(int)*(t+1));
dis[s]=0; q.push(s);
while(!q.empty()){
int x=q.front(); q.pop();
inq[x]=0;
for(int i=head[x];i!=-1;i=e[i].nxt)
if(e[i].cap && dis[e[i].v]>dis[x]+e[i].w){
dis[e[i].v]=dis[x]+e[i].w;
pre[e[i].v]=x; pre_e[e[i].v]=i;
if(!inq[e[i].v]){
inq[e[i].v]=1;
q.push(e[i].v);
}
}
}
return dis[t]!=INF;
}
void Costflow(){
while(SPFA()){
int fff=INF;
for(int i=t;i!=s;i=pre[i])
fff=min(fff,e[pre_e[i]].cap);
for(int i=t;i!=s;i=pre[i]){
e[pre_e[i]].cap-=fff;
e[pre_e[i]^1].cap+=fff;
}
ans+=fff*dis[t];
}
}
连通分量(Tarjan)
1.强连通分量
每次维护一个栈中标记、dfn(访问序号)、low(访问的最浅的节点)和一个栈。一个点的dfn==low从栈顶到这个点都是一个强连通分量。
Code
struct Edge{
int u,v;
int nxt;
}e[maxm];
int head[maxn],stack[maxn],ins[maxn],dfn[maxn],low[maxn],d[maxn],sz[maxn];
int top,n,cnt;
void Tarjan(int x){
dfn[x]=low[x]=++cnt;
stack[++top]=x; ins[x]=1;
for(int i=head[x];i!=-1;i=e[i].nxt)
if(!dfn[x]){
Tarjan(e[i].v);
low[x]=min(low[x],low[e[i].v]);
}
else if(ins[e[i].v])
low[x]=min(low[x],dfn[e[i].v]);
if(dfn[x]==low[x]){
int s;
do{
s=stack[top--];
ins[s]=0;
d[s]=x; sz[x]++;
}while(s!=x);
}
}
2.双连通分量
1.点双连通分量:若一个dfs树上x子节点的low[son]>=low[x],那x为割点,注意特判根节点若超过1个儿子则不为割点。
2.边双连通分量:若一个dfs树上x子节点的low[son]>low[x],则这条边为桥。统计low的同时把边入栈,当找到一个割点时把栈里的边出栈,所有边上的点在一个边双连通分量里。
Code
struct Edge{
int u,v;
int nxt;
}e[maxn<<1];
stack <Edge> s;
bool cut[maxn];//cut[i]=true为割点
int head[maxn],dfn[maxn],low[maxn],d[maxn],bcc[maxn][maxn],cutsum[maxn];
int cnt,ind,tot,ans1;
void Getbcc(int u,int v){//求边双连通分量
Edge E;
cut[u]=true;
bcc[++tot][0]=0;
do{
E=s.top(); s.pop();
if(d[E.u]!=tot){
bcc[tot][++bcc[tot][0]]=E.u;
d[E.u]=tot;
}
if(d[E.v]!=tot){
bcc[tot][++bcc[tot][0]]=E.v;
d[E.v]=tot;
}
}while(E.u!=u || E.v!=v);
}
void Tarjan(int t,int p){
int chsum=0;
dfn[t]=low[t]=++cnt;
for(int i=head[t];i!=-1;i=e[i].nxt)
if(p!=e[i].v){
if(!dfn[e[i].v]){
chsum++;
s.push(e[i]);
Tarjan(e[i].v,t);
low[t]=min(low[t],low[e[i].v]);
if(low[e[i].v]>=dfn[t]) Getbcc(t,e[i].v);
}
else if(dfn[e[i].v]<dfn[t]){
s.push(e[i]);
low[t]=min(low[t],dfn[e[i].v]);
}
}
if(p<0 && chsum==1) cut[t]=0;
}
拓扑排序
每次把入度为0的节点放入到某数据结构中,并更新其他节点的入度。可用于判环,DP。
字符串
KMP
将匹配串与自身匹配求出next数组,再与模式串匹配。
Code
char s[maxn],t[maxn];
int next[maxn];
int lens,lent;
void Getnext(){
next[0]=next[1]=0;
for(int i=1,now=0;i<lent;i++){
while(t[i]!=t[now] && now) now=next[now];
if(t[i]==t[now]) now++;
next[i+1]=now;
}
}
void KMP(){
Getnext();
for(int i=0,now=0;i<lens;i++){
while(s[i]!=t[now] && now) now=next[now];
if(s[i]==t[now]) now++;
if(now==lent) now=next[now];
}
}
Trie树
后缀数组
维护每一个后缀的“字典序”排名和与前一个排名的后缀的LCP的长度。
每一个后缀的前缀都是原本字符串的一个子串。
Code
char ch[maxn];
int sa[maxn],t1[maxn],t2[maxn],c[maxn];
int rank[maxn],height[maxn];
int len;
bool cmp(int *r,int a,int b,int k){
return r[a]==r[b] && r[a+k]==r[b+k];
}
void DA(int n,int m){
int *x=t1,*y=t2;
for(int i=0;i<m;i++) c[i]=0;
for(int i=0;i<n;i++) c[x[i]=ch[i]]++;
for(int i=1;i<m;i++) c[i]+=c[i-1];
for(int i=n-1;i>=0;i--) sa[--c[x[i]]]=i;
for(int k=1;k<=n;k<<=1){
int p1=0,p2=1;
for(int i=n-k;i<n;i++) y[p1++]=i;
for(int i=0;i<n;i++) if(sa[i]>=k) y[p1++]=sa[i]-k;
for(int i=1;i<m;i++) c[i]=0;
for(int i=0;i<n;i++) c[x[y[i]]]++;
for(int i=1;i<m;i++) c[i]+=c[i-1];
for(int i=n-1;i>=0;i--) sa[--c[x[y[i]]]]=y[i];
swap(x,y);
x[sa[0]]=0;
for(int i=1;i<n;i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],k)?p2-1:p2++;
if(p2>=n) return;
m=p2;
}
}
void Getheight(int n){
for(int i=0;i<=n;i++) rank[sa[i]]=i;
for(int k=0,i=0;i<n;i++){
if(k) k--;
int j=sa[rank[i]-1];
while(ch[i+k]==ch[j+k]) k++;
height[rank[i]]=k;
}
}
int main(){
scanf("%s",ch);
len=strlen(ch);
DA(len+1,128);
Getheight(len);
return 0;
}
后缀自动机
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#define maxn 20000+5
using namespace std;
struct SAM{
int am[maxn][26],mx[maxn],fa[maxn];
int last,cnt;
SAM(){ last=cnt=1; }
void extend(int c){
int p=last,np=++cnt; last=np;
mx[np]=mx[p]+1;
for(;p && !am[p][c];p=fa[p]) am[p][c]=np;
if(!p) fa[np]=1;
else{
int q=am[p][c];
if(mx[q]==mx[p]+1) fa[np]=q;
else{
int nq=++cnt;
memcpy(am[nq],am[q],sizeof(am[q]));
mx[nq]=mx[p]+1;
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
for(;p && am[p][c]==q;p=fa[p]) am[p][c]=nq;
}
}
}
}sam;
char s[maxn];
int len;
int main(){
scanf("%s",s+1);
len=strlen(s+1);
for(int i=1;i<=len;i++)
sam.extend(s[i]-'A');
return 0;
}
字符串哈希
//------------------未完待续1s----------------------
马拉车
//------------------未完待续1s----------------------
数论
线性规划(单纯形法)
#include<cmath>
#include<cstdio>
#include<climits>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define maxn 1000+5
#define maxm 10000+5
#define INF (INT_MAX-1)
using namespace std;
const double eps=1e-7;
double a[maxm][maxn],b[maxm],c[maxn];
double ans;
int n,m;
void pivot(int l,int e){
b[l]/=a[l][e];
a[l][e]=1.0/a[l][e];
for(int i=1;i<=n;i++)
if(i!=e) a[l][i]*=a[l][e];
//处理原等式
for(int i=1;i<=m;i++)
if(i!=l && fabs(a[i][e])>eps){
b[i]-=a[i][e]*b[l];
for(int j=1;j<=n;j++)
if(j!=e) a[i][j]-=a[l][j]*a[i][e];
a[i][e]=-a[l][e]*a[i][e];
}
//代入其余等式
ans+=b[l]*c[e];
for(int i=1;i<=n;i++) if(i!=e) c[i]-=c[e]*a[l][i];
c[e]=-c[e]*a[l][e];
//代入目标函数
}
double Simplex(){
while(true){
int e=0,l=0;
double tmp=INF;
for(int i=1;i<=n;i++)
if(c[i]>eps && c[i]>c[e]) e=i;
//贪心加一波ans,快了两三倍
if(e==0) return ans;
for(int i=1;i<=m;i++)
if(a[i][e]>eps && tmp>b[i]/a[i][e]){
l=i; tmp=b[i]/a[i][e];
}
if(tmp==INF) return INF;
pivot(l,e);
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lf",&c[i]);
for(int l,r,i=1;i<=m;i++){
scanf("%d%d",&l,&r);
for(int j=l;j<=r;j++) a[i][j]=1;
scanf("%lf",&b[i]);
}
printf("%d",(int)(Simplex()+0.5));
return 0;
}