Note_4.9

2019/4/9 奇奇怪怪的笔记



关于代码,基本上是现写的,可能连编译都过不了

因为是简单算法场,所以就很不走心了昂,/小纠结



图论相关 



最小生成树

  • prim,kruskal
  • 最小生成树的切割性质
  • 求次小生成树,动态mst
  • 根据Kruskal的贪心性质,任意两个联通块之间的最小边一定在mst中
  • 非mst一定可以通过一次换边得到权值更小的生成树。

最短路

dijkstra​

priority_queue是什么,我不知道

class dijkstra
{
    const ll MN=262144,inf=0x3f3f3f3f
    ll d[N];
    struct node{ll x;int id;}t[MN<<1];
    inline node Min(const node&x,const node&y){return x.x<y.x?x:y;}
    inline void rw(int k,ll x){for(t[k+=MN].x=x;k>>=1;)t[k]=Min(t[k<<1],t[k<<1|1]);}\
    inline void dij()
    {
        register int i,x;
        for(i=1;i<MN<<1;++i) t[i].x=inf;//init
        for(i=1;i<=n;++i) d[t[i+MN].id=i]=inf;//init
        for(rw(S,d[S]=0);t[1]!=inf;)
        {
            rw(x=t[1].id,inf);
            for(i=hr[x];i;i=e[i].nex)
                if(d[e[i].to]>d[x]+e[i].w) rw(e[i].to,d[e[i].to]=d[x]+e[i].w);
        }
    }
}



spfa

SLF优化是什么,我不知道

int dis[MN];
bool in[MN];
std::queue<int> q;
bool spfa()
{
	memset(dis,0x3f,sizeof dis);
	memset(in,0,sizeof in);
	reg int i,j;
	in[T]=true;q.push(T);dis[T]=0;
	while(!q.empty())
	{
		reg int u=q.front();q.pop();in[u]=false;
		for(i=hr[u];i;i=e[i].nex)
			if(dis[e[i].to]>dis[u]-e[i].c&&e[i^1].w)
			{
				dis[e[i].to]=dis[u]-e[i].c;
				if(!in[e[i].to]) in[e[i].to]=true,q.push(e[i].to);
			}	
	}
	return dis[S]!=inf;
}



floyd

复杂度\(O(n^3)\),倍增floyd


图的联通



强联通分量

边/点双联通分量

割点、割边

圆方树

#define son e[i].to
int dfn[MN],low[MN],ind,st[MN],tp;
void tarjan(int x,int F=0)
{
    dfn[x]=low[x]=++ind;st[tp++]=x;
    reg int i;
    for(i=hr[x];i;i=e[i].nex)if(i^F^1)//防止重边的影响
        if(!dfn[son])
        {
            //如果两个点也算双联通分量的话,直接写(low[son]>=dfn[x])
            tj(son,i);low[x]=min(low[x],low[son]);
            if(dfn[x]==low[son])
            {
                ++num;ins(x,num,Hr);
                for(;st[tp]!=son;--tp) ins(num,st[tp-1],Hr);
            }
            else if(low[son]>dfn[x]) --tp,ins(x,son,Hr);
        }
        else low[x]=min(low[x],dfn[son]);
}
  • 两个圆点在圆方树上的路径,与路径上经过的方点相邻的圆点的集合,就等于原图中两点简单路径上的点集
  • 仙人掌上的最短路,圆方树上两个圆点之间权值不变,方点到子节点的边权等于父亲到它的最短路,求距离时,如果\(lca\)时方点,需要暴力转弯



网络流



dinic

\(O(EV^2)\),显然跑不满,对于二分图求匹配这样的特殊情况,复杂度是\(O(E\sqrt V)\)

//懒得写了,反正很熟练了哇
int d[MN],q[MN],top;
bool bfs()
{
    memset(d,0,sizeof d);
    reg int i,j;
    for(d[q[i=top=1]=S]=1;i<=top;++i)
        for(j=hr[q[i]];i;i=e[i].nex)
            if(e[i].w&&!d[e[i].to])
                d[q[++top]=e[i].to]=d[q[i]]+1;
}
int dfs(int x,int f)
{
    if(x==T) return f;
    int used=0;
    for(int &i=cur[x];i;i=e[i].nex)
        if(d[e[i].to]==d[x]+1&&e[i].w)
        {
            int tmp=dfs(e[i].to,min(f-used,e[i].w));
            e[i].w-=tmp;e[i^1].w+=tmp;used+=tmp;
            if(used==f) return used;
        }
    return d[x]=-1,used;
}
int dinic()
{
    reg int maxflow=0;
    while(bfs())
    {
        memcpy(cur,hr,sizeof cur);
        maxflow+=dfs(S,inf);
    }
    return maxflow;
}



费用流spfa

\(O(\alpha VE)\),据说\(\alpha\)平均不超过\(2\)

//假设每条边的费用是时间,那么每次增广相当于增大走路的时间
class Flow
{
	bool vis[N],inq[N];ll dis[N];queue<int>q;
	struct edge{int to,w,c,nex;}e[N*10];int cur[N],hr[N],en;
	bool spfa()
	{
		for(int i=S;i<=T;++i) cur[i]=hr[i],inq[i]=0,dis[i]=1e18;
		for(dis[S]=0,inq[S]=1,q.push(S);!q.empty();)
		{
			int u=q.front();q.pop(),inq[u]=0;
			for(int i=hr[u];i;i=e[i].nex)
			if(dis[e[i].to]>dis[u]+e[i].c&&e[i].w)
			{
				dis[e[i].to]=dis[u]+e[i].c;
				if(!inq[e[i].to]) inq[e[i].to]=1,q.push(e[i].to);
			}
		}
		return dis[T]<inf;
	}
	int dfs(int x,int f)
	{
		if(x==T||!f) return f;vis[x]=1;
		int w,used=0;
		for(int &i=cur[x];i;i=e[i].nex)
		if(dis[e[i].to]==dis[x]+e[i].c&&e[i].w&&!vis[e[i].to])
		{
			w=dfs(e[i].to,min(f-used,e[i].w));
			e[i].w-=w;e[i^1].w+=w;used+=w;
			if(used==f) break;
		}
		vis[x]=0;return used;
	}
    public:
		int S,T;
		Flow(){S=0;en=1;}
		void ins(int x,int y,int w,int c)
		{	dbg1(x);dbg2(y);
			e[++en]=(edge){y,w,c,hr[x]},hr[x]=en;
			e[++en]=(edge){x,0,-c,hr[y]},hr[y]=en;
		}
		ll Ans(){ll r=0;while(spfa())r+=dfs(S,inf)*dis[T];return r;}
};



最小割树

//省去dinic部分
void clear(){for(reg int i=2;i<en;i+=2)e[i].w=e[i^1].w=(e[i].w+e[i^1].w)>>1;}
//因为是无向的图所有一开始正向反向边都有容量
void Build(int *id, int n)
{
    if(n==1) return;
    static int s[MN],t[MN];int cnts=0,cntt=0;
    int cut=dinic(id[0],id[1]);
    ins(id[0],id[1],cut,Hr,E,En);
    for(reg int i=0;i<n;++i)
        if(d[id[i]]) s[cnts++]=id[i];
        else t[cntt++]=id[i];
    memcpy(id,s,cnts<<2);
    memcpy(id+cnts,t,cntt<<2);
    if(cnts) Build(id,cnts);
    if(cntt) Build(id+cnts,cntt);
}



带上下界的网络流?

blog~



树相关



树分治?

不会边分治的蒟蒻。。。

随便放几道题算了:1. 长链剖分 2. 点分治 3. 点分树



LCA?

树剖,倍增

RMQ

int pos[MN],fpos[MN<<2],st[MN<<2][20];//欧拉序,反欧拉序,st表
int dis(int x,int y)
{
    if(pos[x]>pos[y]) std::swap(x,y);
    reg int k=lg[pos[y]-pos[x]+1];
    return fpos[min(st[pos[x]][k],st[pos[y]-(1<<k)+1][k])];
}
void dfs(int x,int fa=0)
{
    pos[x]=++ind;fpos[ind]=x;st[ind][0]=ind;reg int i;
    for(i=hr[x];i;i=e[i].nex)if(e[i].to^fa)
        dfs(e[i].to,x),st[++ind][0]=pos[x];
}
inline void pre_work()
{
    reg int i,j;dfs(1);
    for(lg[0]=-1,i=1;i<(MN<<2);++i)lg[i]=lg[i>>1]+1;//注意是MN<<2
    for(j=1;j<20;++j)for(i-1;i+(1<<j)-1<=ind&&i<=ind;++i)
        st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}



动态树?prufer编码?, 跳转到前天的笔记



欧拉回路

(偷偷把原来写的东西搬上来。。。)

  1. 判定

    有向图:所有点连通,且入度都等于出度
    无向图:所有点连通,且度数为偶数
    欧拉回路删掉一条边就是欧拉路径
    欧拉路径:有且只有两个不符合欧拉回路要求的点,并且相连后就满足了

  2. 算法

    求欧拉路径就把不满足的点连起来,求欧拉回路后删掉这条边就好了

    无向图

    dfs,标记走过的边以及反边,走过就不走

    每次回溯时记录边,这些边依次构成欧拉回路

    有向图

    边反向

    和无向图差不多

    然后可以当前弧优化

  3. 正确性

    除了第一个点外,其它的点dfs是一定是入边出边成对出现
    所以第一次加入答案的边一定是第一个点的入边
    回溯时这个点又会多走一条出边,那么下次加入的一定是又是这个点的入边
    归纳下去,一定会形成一条回路

namespace undir
{
    inline void ins(int u,int v);//略略略
    int de[MN],st[MN],top,mark[MN<<1]; 
    void dfs(int u)
    {
	    for(int &i=hr[u];i;i=e[i].nex)if(!mark[i])
        {
            mark[i]=mark[i^1]=1;int j=i;dfs(e[i].to);//反边反边!
            st[++top]=j&1?((j-1)>>1):j>>1; 
        }
    }
    void solve();//包括读入,判定,略略略
}
namespace dir
{
    inline void ins(int u,int v);//略略略
    int ind[MN],outd[MN],st[MN],top,mark[MN];
    void dfs(int u)
    {
        for(int &i=hr[u];i;i=e[i].nex)if(!mark[i])
        {
            mark[i]=1;int j=i;
            dfs(e[i].to);st[++top]=j-1;
        }
    }
    void solve();
}



匈牙利算法

KM算法是什么,我不知道

核心是找到一个起点和终点都在同一个集合的增广路径,路径上相邻的两条边中恰有一个在匹配中

int ans,bel[MN];
bool vis[MN];
bool find(int x)
{
    for(int i=hr[x];i;i=e[i].nex)
        if(!vis[e[i].to])
        {
            vis[e[i].to]=true;
            if(bel[e[i].to]==-1||find(bel[e[i].to])) return bel[e[i].to]=x,1;
        }
    return 0;
}
void Main()
{
    for(int i=0;i<n;++i)
    {
        memset(vis,0,sizeof vis);
        if(find(i)) ++ans;
    }
}





字符串什么的



KMP

找到最大的前缀等于后缀,\(=\)最大的\(border\)

int nex[MN];
void KMP(char *s,int len)
{
    int i,k=0;
    for(nex[1]=0,i=2;i<=len;++i)
    {
        while(k&&s[k+1]!=s[i])k=nex[k];
        if(s[k+1]==s[i]) nex[i]=++k;
        else nex[i]=0;
    }
}
int March(char *s,int lens,char *t,int lent)
{
    int i,k=0,tot;
    for(i=1;i<=lent;++i)
    {
        while(k&&s[k+1]!=t[i]) k=nex[k];
        if(s[k+1]==t[i]) ++k;
        if(k==lens) ++tot,k=nex[k];//match!
	}
    return tot;
}



Z算法

和manacher类似的算法

\(z[i]\)表示\(s[i...]\)和原串的LCP

已知\(z[1...i-1]\),求\(z[i]\)

定义\(r[i]=i+z[i]\),即 \(i\) 后缀和原串的 LCP 在 \(i\) 后缀中的结束位置加\(1\)

  • 考虑 \(1\)\(i-1\)\(r[k]\) 最大的 \(k\),分成三种情况:
  1. \(i ≥ r[k]\):令 \(z[i] = 0\),往后暴力匹配,更新 \(k\)\(i\)
  2. \(i < r[k]\)\(r[i-k+1] < z[k]\)\(z[i] = z[i-k+1]\)
  3. \(i < r[k]\)\(r[i-k+1] ≥ z[k]\):令 \(z[i] = r[k] - i\),往后暴力匹配,更新 \(k\)\(i\)

均摊 \(O(n)\)

考虑做字符串匹配,令\(C=B+\)'#'\(+A\),求\(z\)数组,看哪些后缀的\(z\)值等于\(len_B\)

void Z()
{
    z[1]=0;r[1]=1;
    int i,k=1;
    for(i=2;i<=N;++i)
    {
        if(r[k]<=i) z[i]=1;
        else z[i]=min(r[k]-i,z[i-z[k]+1]);
        while(i+z[i]<=N&&s[1+z[i]]==s[i+z[i]]) ++z[i];
        r[i]=i+z[i];
        if(r[i]>=r[k]) k=i;
    }
}



AC自动机

class AC_automaton{
    private:
        int tr[N][26],cnt,e[N],fail[N];
    public:
        void insert(char *s)
        {
            int p=0;
            for(int i=0;s[i];i++)
            {
                int k=s[i]-'a';
                if(!tr[p][k])tr[p][k]=++cnt;
                p=tr[p][k];
            }
            e[p]++;
        }
        void build()
        {
            queue<int>q;
            memset(fail,0,sizeof(fail));
            for(int i=0;i<26;i++)if(tr[0][i])q.push(tr[0][i]);
            while(!q.empty())
            {
                int k=q.front();q.pop();
                for(int i=0;i<26;i++)
                    if(tr[k][i])
                        fail[tr[k][i]]=tr[fail[k]][i],q.push(tr[k][i]);
                    else tr[k][i]=tr[fail[k]][i];
            }
        }
        int query(char *t)
        {
            int p=0,res=0;
            for(int i=0;t[i];i++)
            {
                p=tr[p][t[i]-'a'];
                for(int j=p;j&&~e[j];j=fail[j])res+=e[j],e[j]=-1;
            }
            return res;
        }
}ac;



SA

为什么我的\(SA\)这么长啊啊啊

大佬的博客,供哪天突然不会的我学习

\(sa\),基数排序

\(height[i]=LCP(sa[i],sa[i-1])\)

\(h[i]=height[rk[i]]\)

\(h[i]\ge h[i-1]-1\)

class SA
{
    private:
        int height[MN][19],p,q,sa[2][MN],rk[2][MN],num[MN];
    public:
        SA(){M(height);M(sa);M(rk);M(num);}
        inline void init(){M(height);M(sa);M(rk);M(num);}
        inline void build_sa()
        {
            register int i,j,k,mx;
            for(i=1;i<=n;++i) num[s[i]-'a'+1]++;
            for(i=1;i<=26;++i) num[i]+=num[i-1];
            for(i=1;i<=n;++i) sa[1][num[s[i]-'a'+1]--]=i;
            for(i=1;i<=n;++i) rk[1][sa[1][i]]=rk[1][sa[1][i-1]]+(s[sa[1][i-1]]!=s[sa[1][i]]);
            mx=rk[1][sa[1][n]];
            for(p=1,q=0,k=1;k<=n;k<<=1,p^=1,q^=1)
            {
                if(mx==n) break;
                for(i=1;i<=n;++i) num[rk[p][sa[p][i]]]=i;
                for(i=n;i;--i) if(sa[p][i]>k) sa[q][num[rk[p][sa[p][i]-k]]--]=sa[p][i]-k;
                for(i=n-k+1;i<=n;++i) sa[q][num[rk[p][i]]--]=i;
                for(i=1;i<=n;++i)
                    rk[q][sa[q][i]]=rk[q][sa[q][i-1]]+(rk[p][sa[q][i]]!=rk[p][sa[q][i-1]]||rk[p][sa[q][i]+k]!=rk[p][sa[q][i-1]+k]);
                mx=rk[q][sa[q][n]];
            }
            for(i=k=1;i<=n;++i)
            {
                if(rk[p][i]==1) continue;if(k) k--;
                for(j=sa[p][rk[p][i]-1];j+k<=n&&i+k<=n&&s[i+k]==s[j+k];++k);
                height[rk[p][i]][0]=k;
            }
            for(i=1;i<=18;++i)for(j=n;j>=1&&j>(1<<i);--j)
                height[j][i]=min(height[j][i-1],height[j-(1<<i-1)][i-1]);
        }
        inline int LCP(int x,int y)
        {
            x=rk[p][x];y=rk[p][y];
            if(x>y) std::swap(x,y);
            return min(height[y][lg[y-x]],height[x+(1<<lg[y-x])][lg[y-x]]);
        }
}



SAM

核心?

  • 作为一种可以表示所有后缀的状态的自动机,它得满足状态数尽可能的小
  • SAM的做法:
    1. 每个状态表示所有\(Right\)集合相同的子串,这里\(Right\)集合的定义可是一个子串在原串中所有出现位置的右端点的集合。
    2. 对于每个状态,我们定义一个\(step\),表示该状态所能表示的所有子串中长度最大值
    3. \(fa\)指针,满足当前串的\(Right\)集合是\(fa\)指向的状态的真子集,且是最大的那一个,可以发现,\(fa\)指针所指向的状态一定是当前状态子串的一个后缀。
    4. 在线加点,每次加点后最多只会增加两个新的状态
class Suf_Automation
{
    #define MX 2000005
    private:
        int c[MX][26],fa[MX],step[MX],v[MX],rk[MX],val[MX];
        int last,cnt,n;
        ll ans=0;
    public:
        inline void init(int len)
        {
            cnt=last=1;n=len;
            for(int i=1;i<=n<<1;++i)
            memset(c[i],0,sizeof c[i]),step[i]=fa[i]=v[i]=val[i]=0;
        }
        void Insert(int x)
        {
            int p=last,np=++cnt;step[np]=step[p]+1;val[np]=1;
            for(;p&&!c[p][x];p=fa[p]) c[p][x]=np;
            if(!p) fa[np]=1;
            else 
            {
                int q=c[p][x];
                if(step[q]==step[p]+1) fa[np]=q;
                else 
                {
                    int nq=++cnt;step[nq]=step[p]+1;
                    memcpy(c[nq],c[q],sizeof c[q]);
                    fa[nq]=fa[q];fa[np]=fa[q]=nq;
                    for(;c[p][x]==q;p=fa[p]) c[p][x]=nq;
                }    
            }
            last=np;
        }
        inline void Query()
        {
            register int i;
            for(i=1;i<=cnt;++i) ++v[step[i]];
            for(i=1;i<=n;++i) v[i]+=v[i-1];
            for(i=1;i<=cnt;++i) rk[v[step[i]]--]=i;
            for(i=cnt;i;--i)
            {
                val[fa[rk[i]]]+=val[rk[i]];
                if(val[rk[i]]>1) ans=max(ans,1ll*val[rk[i]]*step[rk[i]]);
            }
            val[1]=0;
            printf("%lld\n",ans);
        }
    #undef MX
}pac;



后缀树

咕咕咕



回文树

又称作“回文自动机”

每个节点都是一个回文串,然后\(fail\)指针维护的是它的最大的后缀回文串(同时也是前缀的)。

回文树的用处?

——by \(PinkRabbit\)

  • 统计每个本质不同回文串出现次数的。这个 Manacher 很难做到(需要配合后缀自动机),但是回文自动机可以解决。
  • 有趣的是,回文自动机可以支持前端插入呢,只需要再维护一个指向最长回文前缀的指针 \(head\)就好啦。因为回文是两边对称的呢,所以前端插入也没关系的。注意两个指针要同时更新哦。就是当前字符插完后,整个串形成回文串,那么就要更新\(head\)为当前这一端的\(fail\)

例题:APIO2014 回文串

//APIO 2014 回文串
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read()
{
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x*f;
}
#define MN 300005
int fail[MN],len[MN],cnt[MN];
int c[MN][26],en;ll ans;
char s[MN];
int main()
{
    scanf("%s",s+1);
    fail[0]=1;len[++en]=-1;
    for(int i=1,j=1;s[i];++i)
    {
        while(s[i]!=s[i-len[j]-1]) j=fail[j];
        if(!c[j][s[i]-'a'])
        {
            len[++en]=len[j]+2;
            int k=fail[j];
            while(s[i]!=s[i-len[k]-1])
                k=fail[k];    
            fail[en]=c[k][s[i]-'a'];
            c[j][s[i]-'a']=en;
        }
        j=c[j][s[i]-'a'];
        ++cnt[j];
    }    
    for(int i=en;i>1;i--)    
    {
        ans=max(ans,1ll*cnt[i]*len[i]);
        cnt[fail[i]]+=cnt[i];    
    }
    printf("%lld\n",ans);
    return 0;
}



manacher

\(abcba\rightarrow a!b!c!b!a\)

\(p[i]\)表示以\(i\)为回文中心的左右能延伸的最长长度\(+1\)

\(p[1]=1\)

\(r[i]=i+p[i]\)

考虑 \(1\)\(i-1\)\(r[k]\) 最大的 \(k\),分成三种情况:

  1. \(i \geq r[k]\),令\(p[i]=1\),然后暴力更新,更新 \(k\)\(i\)
  2. \(i < r[k]\)&&\(p[2*k-i]<r[k]-i\)\(p[i]=p[2*k-i]\)
  3. \(i<r[k]\)&&\(p[2*k-i]\geq r[k]-i\),令\(p[i]=r[k]-i\),然后暴力更新,更新 \(k\)\(i\)
 for(k=p[1]=1,i=r[1]=2;i<=m;++i)
 {
     if(r[k]<=i) p[i]=1;
     else p[i]=min(p[k*2-i],r[k]-i);
     for(;s[i+p[i]]==s[i-p[i]];++p[i]);
     r[i]=p[i]+i;r[i]>r[k]?k=i:0;p[i]-1>ans?ans=p[i]-1:0;
 }



后缀平衡树?咕咕咕






Blog来自PaperCloud,未经允许,请勿转载,TKS!

posted @ 2019-04-09 22:33  PaperCloud  阅读(194)  评论(0编辑  收藏  举报