zoukankan      html  css  js  c++  java
  • 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(Esqrt 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
    ",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
    ",ans);
        return 0;
    }
    



    manacher

    (abcba ightarrow 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!

  • 相关阅读:
    下一个ajax异步请求被挂起问题
    借鉴别人的Oracle 11g安装和卸载图文教程
    Html5 实现网页截屏 页面生成图片(图文)
    Oracle修改字段类型方法小技巧
    基于轻量级ORM框架Dapper的扩展说明
    JavaScript+html5 canvas实现本地截图教程
    SkipList跳表基本原理
    Oracle日期查询:季度、月份、星期等时间信息
    设计模式之模板模式
    设计模式之解释器模式
  • 原文地址:https://www.cnblogs.com/PaperCloud/p/10680338.html
Copyright © 2011-2022 走看看