zoukankan      html  css  js  c++  java
  • 【孤*执】#2019SX# 最后der省选模板总结 ——BY.hss

    部分参照这一篇   【浮*光】 #noip总复习#  BY.hss 

    注意省选题常见的思路:

    三分极值 / 二分答案
    贪心 + 多数组的转移转化
    正逆序处理:倒推法
    找规律,确定单调性
    多区间问题的处理:倍增法(RMQ)

    【重点中的重点】

    (1)离散化

    int kt[N],a[N]; //辅助数组kt[]
     
    int main(){
        for(int i=1;i<=n;i++) cin>>a[i],kt[i]=a[i];
        sort(kt+1,kt+n+1); //辅助数组进行排序
        m=unique(kt+1,kt+n+1)-kt-1; //注意要-kt-1
        for(int i=1;i<=n;i++) //↓↓第一个大于等于a[i]的位置
            a[i]=lower_bound(kt+1,kt+m+1,a[i])-kt; //注意只用-kt
    }

    (2)线段树

    struct SegmentTree{ int l,r,sum; }tree[4*N];
    
    void PushUp(int rt){ tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum; }
    
    void build(int l,int r,int rt){ //【建树】
        tree[rt].l=l; tree[rt].r=r; //建立标号与区间的关系
        if(l==r){ scanf("%d",&tree[rt].sum); return; } //叶子节点
        int mid=(l+r)/2; build(l,mid,rt<<1),build(mid+1,r,rt<<1|1);
        PushUp(rt); //将修改值向上传递
    }
    
    void add(int p,int rt){ //【单点修改】
        if(tree[rt].l==tree[rt].r){ tree[rt].sum+=y; return; } //叶子节点
        int mid=(tree[rt].l+tree[rt].r)>>1;
        if(p<=mid) add(p,rt<<1); else add(p,rt<<1|1);
        tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum; //pushup
    }
    
    void query(int p,int rt){ //【单点查询】
        if(tree[rt].l==tree[rt].r){ ans=tree[rt].sum; return; } //叶子节点
        int mid=(tree[rt].l+tree[rt].r)>>1;
        if(p<=mid) query(p,rt<<1); else query(p,rt<<1|1);
    }
    
    void sum(int rt){ //【区间查询求和】
        if(tree[rt].l>=x&&tree[rt].r<=y) //区间完全包含
          { ans+=tree[rt].sum; return; }
        int mid=(tree[rt].l+tree[rt].r)>>1;
        if(x<=mid) sum(rt<<1); //区间部分重叠,递归左右
        if(y>=mid+1) sum(rt<<1|1);
    }
    void PushDown(int l,int r,int rt){ //tag是区间修改的标记
        int mid=(l+r)>>1; if(tree[rt].tag==-1||l==r) return;
        tree[rt<<1].tag=tree[rt<<1|1].tag=tree[rt].tag,
        tree[rt<<1].ans=(tree[rt].tag==0)?(mid-l+1):0;
        tree[rt<<1|1].ans=(tree[rt].tag==0)?(r-mid):0;
        tree[rt<<1].l=tree[rt<<1].r=tree[rt<<1].ans;
        tree[rt<<1|1].l=tree[rt<<1|1].r=tree[rt<<1|1].ans;
        tree[rt].tag=-1; //标记每次下移一位,并清空上一位置的标记
    }
    比较复杂的push_down(【p2894】酒店)

    (3)树状数组

    void add(ll x,ll k) //单点修改、维护前缀和
      { for(i=x;i<=n;i+=i&-i) c[i]+=k; }
     
    ll query(ll x) //区间查询、查询前缀和
      { ll sum=0; for(i=x;i>0;i-=i&-i) sum+=c[i]; return sum; }

    (4)主席树

    主程序中: for(int i=1;i<=n;i++) //对应颜色的位置上+1
          reads(col[i]),update(rt[i-1],rt[i],1,MAX,col[i],1);
    
    void update(int las_,int &now_,int l,int r,int val,int op){
        now_=++node_cnt; //主席树动态开点:寻找新值val的位置,建出这条新链
        ls[now_]=ls[las_],rs[now_]=rs[las_],sum[now_]=sum[las_]+op;
        if(l==r) return; int mid=(l+r)>>1;
        if(val<=mid) update(ls[las_],ls[now_],l,mid,val,op);
        else update(rs[las_],rs[now_],mid+1,r,val,op);
    }
    
    int query(int u,int v,int l,int r,int k){ //查询区间中颜色k的个数
        if(l==r) return sum[v]-sum[u]; int mid=(l+r)>>1;
        if(k<=mid) return query(ls[u],ls[v],l,mid,k);
        else return query(rs[u],rs[v],mid+1,r,k); //递归子树寻找位置k
    } //query(rt[l-1],rt[r],1,MAX,k) //寻找区间[l,r]中颜色k的个数

    (5)Trie

    bool tail[SIZE]; //标记串尾元素
    int trie[SIZE][26],tot=1; //SIZE:字符串最大长度(层数)
    //tot为节点编号,用它可以在trie数组中表示某层的某字母是否存在
     
    void insert(char* ss){ //插入一个字符串
        int len=strlen(ss),p=1; //p初始化为根节点1
        for(int k=0;k<len;k++){
            int ch=ss[k]-'a'; //小写字符组成串的某个字符,变成数字
            if(trie[p][ch]==0) trie[p][ch]=++tot; //trie存编号tot
            //↑↑↑不存在此层的这个字符,新建结点,转移边
            p=trie[p][ch]; //指针移动,连接下一个位置
        } tail[p]=true; //s中字符扫描完毕,tail标记字符串的末位字符(的编号p)
    }
     
    bool searchs(char* ss){ //检索字符串是否存在
        int len=strlen(ss),p=1; //p初始化为根节点
        for(int k=0;k<len;k++){
            p=trie[p][ss[k]-'a']; //寻找下一处字符
            if(p==0) return false; //某层字符没有编号,不存在,即串也不存在
        } return tail[p]; //判断最后一个字符所在的位置是否是某单词的末尾
    }

    (6)KMP

    void pre(){ //【预处理nextt[i]】
        nextt[1]=0; int j=0; //j指针初始化为0
        for(int i=1;i<m;i++){ //a数组自我匹配,从i+1=2与1比较开始
            while(j>0&&a[i+1]!=a[j+1]) j=nextt[j];
            //↑自身无法继续匹配且j还没减到0,考虑返回匹配的剩余状态
            if(a[i+1]==a[j+1]) j++; //这一位匹配成功
            nextt[i+1]=j; //记录这一位向前的最长匹配
        }
    }
     
    void kmp(){ //在b串中寻找a串出现的位置
        int ans=0,j=0;
        for(int i=0;i<n;i++){ //扫描b,寻找a的匹配
            while(b[i+1]!=a[j+1]&&j>0) j=nextt[j];
            //↑不能继续匹配且j还没减到0(之前的匹配有剩余状态)
            if(b[i+1]==a[j+1]) j++; //匹配加长,j++
            if(j==m){ //【一定要把这个判断写在j++的后面!】
                printf("%d
    ",i+1-m+1); //子串a的起点在母串b中的位置
                j=nextt[j]; //继续寻找匹配
            } //【↑↑巧妙↑↑这里不用返回0,只用返回上一匹配值】
        } //注意:如果询问串的不重叠出现次数,则j必须变成0
    }

    (7)后缀数组

    const int maxn=500019; int n,m; char s[maxn];
    
    int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){ //(1)基数排序
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){ //(2)后缀排序
        for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); // swap(rank,tp); 
            for(int i=1;i<=n;i++) //注意此处数组交换的方式
                b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                    &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    void getH(){ //(3)求height[]
        int k=0; for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(s[i+k]==s[j+k]) k++; height[rank[i]]=k;  }
    } 
    
    //注意:此模板字符串从1位置开始,即scanf("%s",s+1);

    (8)树链剖分

    void dfs1(ll u,ll fa_){ //第一遍dfs:求子树大小和重儿子
        siz[u]=1,fa[u]=fa_,dep[u]=dep[fa_]+1;
        for(ll i=head[u];i;i=e[i].nextt){
            if(e[i].ver==fa_) continue;
            dfs1(e[i].ver,u),siz[u]+=siz[e[i].ver]; //计算size
            if(siz[e[i].ver]>siz[son[u]]) son[u]=e[i].ver; //重儿子
        }
    }
    
    void dfs2(ll u,ll fa_){ //第二遍dfs:确定dfs序和top值
        if(son[u]){ //先走重儿子,使重链上的各节点在线段树中的编号连续
            seg[son[u]]=++seg[0]; //节点编号、记入线段树中
            rev[seg[0]]=son[u]; //记录对应的原始编号
            top[son[u]]=top[u],dfs2(son[u],u); 
            //↑↑此位置是已有的重链的节点,更新top值,继续递归
        } for(ll i=head[u];i;i=e[i].nextt){ //递归轻边
            if(top[e[i].ver]) continue; //除去u的重儿子或父亲
            seg[e[i].ver]=++seg[0],rev[seg[0]]=e[i].ver; //加入线段树
            top[e[i].ver]=e[i].ver,dfs2(e[i].ver,u);
            //↑↑先递归到的轻边上的点(dep值min),所在重链的top一定是自己
        }
    }
    
    ll query(ll x,ll y){ //路径询问
        ll fx=top[x],fy=top[y],ans=0;
        while(fx!=fy){ //不在同一重链上,选择深度较大的跳到重链top的fa
            if(dep[fx]<dep[fy]) swap(x,y),swap(fx,fy); 
            ans=ans+get(1,1,seg[0],seg[fx],seg[x]); //边跳边统计答案
            x=fa[fx],fx=top[x]; //往上跳、并更新当前所在点的top值(所在重链)
        } if(dep[x]>dep[y]) swap(x,y); //x、y已在同一条重链上 
        ans=ans+get(1,1,seg[0],seg[x],seg[y]); return ans; //直接统计
    }

    (9)网络流最大流

    • 最小割 = 最大流 ;(有正负权值时)最大权闭合子图 = s连的正权值之和 - min_cut 。
    int s,t,tot=1,n,m,ans,head[N],dep[N],cur[N]; //s为源点,t为汇点 
    
    struct node{ int nextt,ver,w; }e[M];
    
    void add(int x,int y,int z)
     { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }
    
    bool bfs(){
        memset(dep,0,sizeof(dep)); //dep记录深度 
        memcpy(cur,head,sizeof(head));
        queue<int> q; while(!q.empty()) q.pop();
        dep[s]=1; q.push(s);
        while(!q.empty()){
            int u=q.front(); q.pop();
            for(int i=head[u];i;i=e[i].nextt)
                if((e[i].w>0)&&(dep[e[i].ver]==0)) //分层 
                    dep[e[i].ver]=dep[u]+1,q.push(e[i].ver);
        } if(dep[t]!=0) return 1;
          else return 0; //此时不存在分层图也不存在增广路
    }
    
    int dfs(int u,int lastt){ int cnt=0;
        if(u==t) return lastt; //lastt:此点还剩余的流量
        for(int i=cur[u];i&&cnt<lastt;i=e[i].nextt){ 
            cur[u]=i; //当前弧优化 
            if((dep[e[i].ver]==dep[u]+1)&&(e[i].w!=0)){
                int f=dfs(e[i].ver,min(lastt-cnt,e[i].w)); 
                if(f>0){ e[i].w-=f,e[i^1].w+=f; cnt+=f; } }
        } if(cnt<lastt) dep[u]=-1; return cnt;
    }
    
    void dinic(){ ans=0; while(bfs()) ans+=dfs(s,2e9); }

    (10)网络流费用流

    struct edge{ ll ver,nextt,flow,cost; }e[2*N];
    
    ll tot=-1,n,m,S,T,maxf=0,minc=0;
    
    ll flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(ll a,ll b,ll f,ll c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(ll S,ll T){
        queue<ll> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            ll x=q.front(); q.pop(); inq[x]=0;
            for(ll i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            ll now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    // 注意:tot=1,memset(head,-1,sizeof(head)); 

    (11)二分图匹配

    for(int i=1;i<=n;i++) //加入左侧每个节点,判断是否存在增广路
        memset(vis,false,sizeof(vis)),ans+=dfs(i); //计算最大匹配边数
    
    bool dfs(int x){
      for(int i=head[x];i;i=e[i].nextt) //寻找连边
        if(!vis[e[i].ver]){ //当前右节点在新左节点的匹配中未访问过
          vis[e[i].ver]=true; //标记这个未访问过的右边点
          if(!match[e[i].ver]||dfs(match[e[i].ver])) //如果空闲 或 原匹配的点可以让位
           { match[e[i].ver]=x; return true; } //左节点x可以占用这个右节点y
        } return false; //无法找到匹配,即该情况下不会出现增广路
    }

    (12)基环树找环

    vector<int> G[MAXN]; //基环树
    
    int fa[MAXN]; //dfs时的父亲
    int dfn[MAXN], idx;  //访问的时间
    int loop[MAXN], cnt; //
    
    void get_loop(int u) {
        dfn[u] = ++ idx; //记录dfn序
        for (int i = 0; i < G[u].size(); i ++) {
            int v = G[u][i]; if(v == fa[u]) continue ;
            if(dfn[v]) { //找到了环
                if(dfn[v] < dfn[u]) continue ;
                loop[++ cnt] = v;
                for ( ; v != u; v = fa[v])
                    loop[++ cnt] = fa[v];
            } else fa[v] = u, get_loop(v); //继续递归
        }
    }

    (13)欧拉函数 单点值 / 线性筛法

    int euler(int x){
        int ans=x;
        for(int i=2;i*i<=x;i++)
            if(x%i==0){
                ans=ans/i*(i-1);
                while(x%i==0) x/=i;
        } if(x>1) ans=ans/x*(x-1);
        return ans; //返回ϕ(x)的值
    } // ϕ(n) = n∗∏(i=1~k)((ai-1)/ai);
    
    --------------------------------------------------------
    
    int phi[MAXN],vis[MAXN],prime[MAXN],tot=0;
    
    void GetPhi(int n){ //【phi线性筛法】
        phi[1]=1; //特例:ϕ(1)=1;
        for(int i=2;i<=n;i++){
            if(!vis[i]) prime[++tot]=i,phi[i]=i-1; //i是素数(1)
            for(int j=1;j<=tot&&i*prime[j]<=n;j++){ //枚举“i*质数”
                vis[i*prime[j]]=1; //标记“i*质数”为合数
                if(i%prime[j]==0) //(3)注意:剪枝,赋值后直接break;
                 {phi[i*prime[j]]=phi[i]*prime[j];break;}
                else phi[i*prime[j]]=phi[i]*(prime[j]-1); } //(2)
        } for(int i=1;i<=n;i++) cout<<phi[i]<<endl;
    }

    (14)凸包

    struct point{ double x,y; }a[10019];
    
    int sta[10019],top,n; double ans=0.0;
    
    double cross(point p0,point p1,point p2) //计算向量叉积
     { return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x); }
    
    double dis(point p1,point p2)  //计算点p1p2的距离
     { return sqrt((double)(p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y)); }
    
    bool cmp(point p1,point p2){ //进行极角排序
        double tmp=cross(a[0],p1,p2); if(tmp>0) return true;
        else if(tmp==0&&dis(a[0],p1)<dis(a[0],p2)) return true;
        else return false; //↑↑若角度相同,则距离小的在前面
    }
    
    void init(){ //输入,并把最左下方的点放在a[0],进行极角排序。 
        point p0; scanf("%lf%lf",&a[0].x,&a[0].y);
        p0.x=a[0].x; p0.y=a[0].y; int k=0;
        for(int i=1;i<n;i++){ scanf("%lf%lf",&a[i].x,&a[i].y);
            if( (p0.y>a[i].y) || ((p0.y==a[i].y)&&(p0.x>a[i].x)) )
                p0.x=a[i].x,p0.y=a[i].y,k=i; //寻找左下角的点
        } a[k]=a[0],a[0]=p0; //把原来0位置的点放到k位置(互换位置)
        sort(a+1,a+n,cmp); //除去0号点,其余n-1个点进行极角排序
    }     
    
    void graham(){ //极角排序法求凸包
        if(n==1) top=0,sta[0]=0;
        if(n==2) top=1,sta[0]=0,sta[1]=1;
        if(n>2){ top=1,sta[0]=0,sta[1]=1;
          for(int i=2;i<n;i++){
            while(top>0&&cross(a[sta[top-1]],a[sta[top]],a[i])<=0) top--;
            top++; sta[top]=i; } //每加入新的点,判断要出栈的凹点,并将新点入栈
        }    
    }    
    
    int main(){
        scanf("%d",&n); init(); graham(); //输入+极角排序+求凸包
        for(int i=0;i<top;i++) ans+=dis(a[sta[i]],a[sta[i+1]]);
        ans+=dis(a[sta[0]],a[sta[top]]); printf("%.2lf
    ",ans); //凸包总周长
    }

    【一、搜索】

    (1)dfs常见思路

    1.确定dfs的边界(或剪枝) 2.记忆化搜索(或剪枝)

    3.枚举方向(判断超界) 4.回溯(所有状态完全回溯)

    vis[xx][yy]=true; dfs(xx,yy,...); vis[xx][yy]=false;

    (2)树上dfs

    ------可用于 lca的pre_dfs 和 各种各样的树形dp 和 树链剖分

        void pre_dfs(int u,int fa_){
            for(int i=head[u];i;i=e[i].nextt){
                int v=e[i].ver; //找到下一条相连的边
                if(v==fa_) continue;
                dep[v]=dep[u]+1; //深度
                dist[v]=dist[u]+e[i].w; //距离
                fa[v]=u; pre_dfs(v,u); //记录father,递归
            }
        }

    (3)bfs常见思路

    1.起点入队,并标记访问(可能不止一个) 2.队首元素向外扩展:head++ 。

    3.枚举方向,判断超界及可行性,标记访问,答案累加,节点入队:tail++ 。

        void bfs(int sx,int sy){ //BFS确定连通块
            node now1; now1.x=sx,now1.y=sy,q.push(now1);
            vis[sx][sy]=1,flag[sx][sy]=tot,num[tot]++;
            maps[tot][num[tot]]=now1; //记录每个连通块中每个点的坐标
            while(!q.empty()){ //进行BFS
                node now=q.front(),now1;q.pop();
                for(int i=0;i<4;i++){ //上、下、左、右
                    int xx=now.x+dx[i],yy=now.y+dy[i];
                    if(!in_(xx,yy)||vis[xx][yy]||ss[xx][yy]!='X') continue;
                    now1.x=xx,now1.y=yy,q.push(now1),
                    vis[xx][yy]=1,flag[xx][yy]=tot;
                    num[tot]++,maps[tot][num[tot]]=now1; //进队并记录信息
                }
            }
        }                            ---------洛谷【p3070】岛游记

    (4)二分常见思路

    1.用于最小化最大值/最大化最小值。 2.设定l、r、mid,进行二分。

    3.设置checks函数,判断是否可行。 4.更新ans,缩小区间l、r。

    • 整数二分、实数二分

        while(l<=r){ int mid=(l+r)>>1; if(check(mid)) ans=mid,r=mid-1; else l=mid+1; }

        while(r-l>1e-8){ mid=(l+r)/2.0; if(checks(mid)) l=mid; else r=mid; }

    (5)二分图染色 / 判定

        bool dfs(int v,int c){
            color[v]=c; //把该点染成颜色c(1或-1)
            for(int i=0;i<G[v].size();i++){
                if(color[G[v][i]]==c) return false; //当前点与相邻点同色
                if(color[G[v][i]]==0&&!dfs(G[v][i],-c))
                    return false; //如果当前点的邻点还没被染色,就染成-c
            } return true; //连通的点全部完成染色
        }
    
        void solve(){
            for(int i=0;i<V;i++)
              if(color[i]==0) if(!dfs(i,1))
                { cout<<"no"<<endl; return; }
            cout<<"yes"<<endl;
        }

    (6)二分图匹配

    <1>最大匹配

    • 匹配:“任意两条边没有公共端点”的边的集合。
    • 最大匹配:边数最多的“匹配”;完美匹配:两侧节点一一对应的匹配。
    • 最大点独立集:两边点数相同时,左边节点的个数n-最大匹配边数。

    main函数中的循环(每次清空vis数组):

    for(int i=1;i<=n;i++) //加入左侧每个节点,判断是否存在增广路
       memset(vis,false,sizeof(vis)),ans+=dfs(i); //计算最大匹配边数

    dfs寻找最大匹配(bool类型,维护match数组):

    bool dfs(int x){
      for(int i=head[x];i;i=e[i].nextt) //寻找连边
        if(!vis[e[i].ver]){ //当前右节点在新左节点的匹配中未访问过
          vis[e[i].ver]=true; //标记这个未访问过的右边点
          if(!match[e[i].ver]||dfs(match[e[i].ver])) //如果空闲 或 原匹配的点可以让位
           { match[e[i].ver]=x; return true; } //左节点x可以占用这个右节点y
        } return false; //无法找到匹配,即该情况下不会出现增广路
    }

    <2>最小链覆盖与反链

    • 反链:一个点集,其中任意两个点都不在同一条链上。
    • 覆盖:所有点都能分布在链上时,需要的最小链数。

    【最小链覆盖数 = 最长(反链)长度】【最长链长度 = 最小(反链)覆盖数】

        -------> 所以求反链可以转化为:求 最小链覆盖数 或 最长链长度。

    【求最小链覆盖(最长反链)】二分图求最大匹配。

    相当于把每个点拆成两个点,求最大点独立集的大小。

    两边点数相同时,最大点独立集大小=左边点数n-最大匹配数。

    【输出最小链覆盖的方案】整体思路是考虑合并原来拆开的两个点。

    用vis数组来标记被右边的某个点匹配上了的左边点。

    那么在左边却没有匹配上的点,肯定是某条链的端点(这个点最多只有一条边在链上)。

    dfs每个在左边并且没有匹配上的点 i,找它在右边的对应端点 i(合并拆成的两个点)。

    寻找右边的 i 有没有匹配(找链的连向...),dfs,直到右边的某个 x 没有匹配,

    那么就说明到了此链的另一个端点。过程中输出选点情况即可。

    void dfs2(int now){ //最小链覆盖的方案
        if(!match[now]){ printf("%d ",now); return; }
        dfs2(match[now]); printf("%d ",now); //↓↓即最小链覆盖的方案
    } //相当于将一开始分开的两个点合并起来,按照匹配路径,寻找每条链的链长

    (7)归并排序-逆序对模板

    int a[maxn],ranks[maxn],ans=0; //ans记录逆序对的数量
     
    void Merge(int l,int r){ //归并排序
        if(l==r) return;
        int mid=(l+r)/2; //分治思想
        Merge(l,mid); Merge(mid+1,r); //递归实现
        int i=l,j=mid+1,k=l;
        while(i<=mid&&j<=r){
            if(a[i]>a[j]){
                ranks[k++]=a[j++];
                ans+=mid-i+1; //逆序对的个数
            } else ranks[k++]=a[i++];
        } while(i<=mid) ranks[k++]=a[i++];
          while(j<=r) ranks[k++]=a[j++];
        for(int i=l;i<=r;i++) a[i]=ranks[i]; //排序数组传入原a数组中
    }

    (8)离散化模板

    int kt[N],a[N]; //辅助数组kt[]
     
    int main(){
        for(int i=1;i<=n;i++) cin>>a[i],kt[i]=a[i];
        sort(kt+1,kt+n+1); //辅助数组进行排序
        m=unique(kt+1,kt+n+1)-kt-1; //注意要-kt-1
        for(int i=1;i<=n;i++) //↓↓第一个大于等于a[i]的位置
            a[i]=lower_bound(kt+1,kt+m+1,a[i])-kt; //注意只用-kt
    }

    【二、字符串】

     https://www.cnblogs.com/FloraLOVERyuuji/p/10574154.html

    (1)字符串哈希

    H(C)=(c1*b^(m-1)+c2*b^(m-2)+....+cm*b^0) mod h。

    • b为基数(base),H(C)的处理相当于把字符串看成b进制数。

    预处理的过程通过递归计算:H(C,k)=H(C,k-1)*b+ck。

    判断某段字符与另一匹配串是否匹配,即判断:

    (↑↑某段字符:从位置k+1开始的长度为n的子串C’=ck+1 ck+2 .... ck+n;)

                 H(C’) =H(C,k+n)-H(C,k)*b^n 与 H(S) 的关系。

    判断回文:正反hash。反hash要倒序预处理,注意左右边界。

    ull自然溢出:powers数组设成ull类型,超出ull时会自然溢出(省时)。

    哈希散列表:取余法,用链表记录每个hash值所在的位置(即对应的余数)。

    (2)KMP模式匹配

    题目:给你两个字符串,寻找其中一个字符串是否包含另一个字符串。

        <1>原短字符串a的【自我匹配】

    void pre(){ //【预处理nextt[i]】
        nextt[1]=0; int j=0; //j指针初始化为0
        for(int i=1;i<m;i++){ //a数组自我匹配,从i+1=2与1比较开始
            while(j>0&&a[i+1]!=a[j+1]) j=nextt[j];
            //↑自身无法继续匹配且j还没减到0,考虑返回匹配的剩余状态
            if(a[i+1]==a[j+1]) j++; //这一位匹配成功
            nextt[i+1]=j; //记录这一位向前的最长匹配
        }
    }

        <2>【原串a与询问串b】的匹配

    • 在b串中寻找a串出现的位置:
    void kmp(){ //在b串中寻找a串出现的位置
        int ans=0,j=0;
        for(int i=0;i<n;i++){ //扫描b,寻找a的匹配
            while(b[i+1]!=a[j+1]&&j>0) j=nextt[j];
            //↑不能继续匹配且j还没减到0(之前的匹配有剩余状态)
            if(b[i+1]==a[j+1]) j++; //匹配加长,j++
            if(j==m){ //【一定要把这个判断写在j++的后面!】
                printf("%d
    ",i+1-m+1); //子串a的起点在母串b中的位置
                j=nextt[j]; //继续寻找匹配
            } //【↑↑巧妙↑↑这里不用返回0,只用返回上一匹配值】
        } //注意:如果询问串的不重叠出现次数,则j必须变成0
    }
    • 求b串与a串匹配的最大长度:
    int kmp(){ int j=0; //求f[i]数组
        for(int i=0;i<n;i++){ //扫描长串b
        while(( j==m || b[i+1]!=a[j+1] ) && j>0) j=nextt[j];
        //↑不能继续匹配且j还没减到0(之前的匹配有剩余状态)或 a在b中找到完全匹配
        
        if(b[i+1]==a[j+1]) j++; //匹配加长,j++
        f[i+1]=j; //此位置及之前与原串组成的最长匹配
     
        // (if(f[i+1]==m),此时a在b中找到完全匹配)
    }

    【拓展】循环同构串的最小表示法 (kmp思想)

    (3)Trie字典树

    Trie树:一种用于实现字符串快速检索的多叉树结构。

    bool tail[SIZE]; //标记串尾元素
    int trie[SIZE][26],tot=1; //SIZE:字符串最大长度(层数)
    //tot为节点编号,用它可以在trie数组中表示某层的某字母是否存在
     
    void insert(char* ss){ //插入一个字符串
        int len=strlen(ss),p=1; //p初始化为根节点1
        for(int k=0;k<len;k++){
            int ch=ss[k]-'a'; //小写字符组成串的某个字符,变成数字
            if(trie[p][ch]==0) trie[p][ch]=++tot; //trie存编号tot
            //↑↑↑不存在此层的这个字符,新建结点,转移边
            p=trie[p][ch]; //指针移动,连接下一个位置
        } tail[p]=true; //s中字符扫描完毕,tail标记字符串的末位字符(的编号p)
    }
     
    bool searchs(char* ss){ //检索字符串是否存在
        int len=strlen(ss),p=1; //p初始化为根节点
        for(int k=0;k<len;k++){
            p=trie[p][ss[k]-'a']; //寻找下一处字符
            if(p==0) return false; //某层字符没有编号,不存在,即串也不存在
        } return tail[p]; //判断最后一个字符所在的位置是否是某单词的末尾
    }
    • 难题:【bzoj4260】按位异或(trie树维护异或前缀和)
    • 难题:【p3065】第一(拓扑排序+trie树)

    (4)AC自动机

    //统计在文本串中出现次数最多的单词。
    
    int n,cnt=0,q[100019]; string tmp[100019];
    
    struct node{ int fail,end,ch[30]; }trie[100019];
    
    struct Result{ int num,pos; }ans[100019]; //所有单词的出现次数
    
    bool operator <(Result a,Result b){
        if(a.num!=b.num) return a.num>b.num;
        else return a.pos<b.pos;
    }
    
    void make_trie(string tmp,int id){ //Trie树
        int len=tmp.length(),p=0;
        for(int i=0;i<len;i++){
            int s=tmp[i]-'a';
            if(!trie[p].ch[s]) trie[p].ch[s]=++cnt;
            p=trie[p].ch[s];
        } trie[p].end=id;
    }
    
    void get_fail(){
        int l=0,r=0; //广搜head、tail指针
        for(int i=0;i<26;i++) if(trie[0].ch[i]!=0)
            trie[trie[0].ch[i]].fail=0,q[++r]=trie[0].ch[i];
        while(l<r){ int p=q[++l];
            for(int i=0;i<26;i++){ int v=trie[p].ch[i];
                if(v) trie[v].fail=trie[trie[p].fail].ch[i],q[++r]=v; //记录fail指针
                else trie[p].ch[i]=trie[trie[p].fail].ch[i]; //“虚指针”
            }
        }
    }
    
    void AC(string tmp){ //文本串匹配
        int len=tmp.length(),p=0;
        for(int i=0;i<len;i++){
            p=trie[p].ch[tmp[i]-'a']; int v=p;
            while(v) ans[trie[v].end].num++,v=trie[v].fail;
        }
    }
    
    int main(){
        while(519){ cin>>n; if(n==0) break;
            cnt=0; memset(trie,0,sizeof(trie));
            for(int i=1;i<=n;i++){ cin>>tmp[i];
                ans[i].num=0,ans[i].pos=i,make_trie(tmp[i],i); }
            trie[0].fail=0; get_fail();
            cin>>tmp[0]; AC(tmp[0]);
            sort(&ans[1],&ans[n+1]); //按出现次数从大到小排序 
            cout<<ans[1].num<<endl<<tmp[ans[1].pos]<<endl;
            for(int i=2;i<=n;i++){ //出现次数相同的所有单词
                if(ans[i].num!=ans[i-1].num) break;
                cout<<tmp[ans[i].pos]<<endl;
            }
        }
    }

    (5)Manacher算法

    void Manacher(){ //求最长回文子串的长度
        t[0]='$',t[1]='#'; //【1】加入'#'
        for(int i=0;i<n;i++) t[i*2+2]=ss[i],t[i*2+3]='#';
        n=n*2+2,t[n]='%'; //更新字符串长度
        int last_max=0,last_id=0; //【2】求出p[]数组
        for(int i=1;i<n;i++){ //↓↓继承i关于id的对称点j的最长匹配长度
            p[i]=(last_max>i)?min(p[2*last_id-i],last_max-i):1;
            while(t[i+p[i]]==t[i-p[i]]) p[i]++; //然后p[i]自身进行拓展
            if(last_max<i+p[i]) last_max=i+p[i],last_id=i; //更新mx和id
            ans_Len=max(ans_Len,p[i]-1); //最长回文子串的长度
        }
    }

    (6)后缀数组

    https://www.cnblogs.com/FloraLOVERyuuji/p/10382408.html

    const int maxn=500019; int n,m; char s[maxn];
    
    int rank[maxn],b[maxn],sa[maxn],tp[maxn],tax[maxn],height[maxn];
    
    void qsort(){ //(1)基数排序
        for(int i=0;i<=m;i++) tax[i]=0;
        for(int i=1;i<=n;i++) tax[rank[i]]++;
        for(int i=1;i<=m;i++) tax[i]+=tax[i-1];
        for(int i=n;i>=1;i--) sa[tax[rank[tp[i]]]--]=tp[i];
    }
    
    void SuffixSort(){ //(2)后缀排序
        for(int i=1;i<=n;i++) rank[i]=s[i],tp[i]=i;
        m=519; qsort(); //第一次基数排序
        for(int k=1;k<=n;k<<=1){
            int p=0; for(int i=n-k+1;i<=n;i++) tp[++p]=i;
            //for(int i=1;i<=k;i++) tp[++p]=n-k+i;
            for(int i=1;i<=n;i++) if(sa[i]>k) tp[++p]=sa[i]-k;
            qsort(); // swap(rank,tp); 
            for(int i=1;i<=n;i++) //注意此处数组交换的方式
                b[i]=tp[i],tp[i]=rank[i],rank[i]=b[i]; rank[sa[1]]=p=1;
            for(int i=2;i<=n;i++)
                rank[sa[i]]=(tp[sa[i]]==tp[sa[i-1]]
                    &&tp[sa[i]+k]==tp[sa[i-1]+k])?p:++p;
            if(p>=n) break; m=p;
        }
    }
    
    void getH(){ //(3)求height[]
        int k=0; for(int i=1;i<=n;i++){
            if(k) k--; int j=sa[rank[i]-1];
            while(s[i+k]==s[j+k]) k++; height[rank[i]]=k;  }
    } 
    
    //注意:此模板字符串从1位置开始,即scanf("%s",s+1);

    【三、数据结构】

    (1)树状数组

    <1> 单点修改,区间查询:ans=query(y)-query(x-1)。

    void add(ll x,ll k) //单点修改、维护前缀和
      { for(i=x;i<=n;i+=i&-i) c[i]+=k; }
     
    ll query(ll x) //区间查询、查询前缀和
      { ll sum=0; for(i=x;i>0;i-=i&-i) sum+=c[i]; return sum; }

    <2> 区间修改,单点查询:c[x]被设置为差分数组前缀和,初始化为0。

    区间修改:add(x,k),add(y+1,-k); 单点查询:ans=a[x]+query(x);

    <3> 区间修改,区间查询:维护两个数组的前缀和。

    sum1[i]=d[i]; sum2[i]=d[i]∗i; (d是差分数组)

    直接把a数组处理成前缀和的形式(省略sum数组):

    scanf("%lld",&a[i]),a[i]+=a[i-1];

    区间修改:add(x,k),add(y+1,-k);

    区间查询:query(y)-query(x-1)+a[y]-a[x-1];

    每次用【差分】思路修改时:sum1[x]+k,sum1[y+1]-k ; sum2[x]+x*k,sum2[y+1]-(y+1)*k。

    void add(ll x,ll k) //维护(差分数组的)区间前缀和
     { for(int i=x;i<=n;i+=i&-i) sum1[i]+=k,sum2[i]+=x*k; }

    查询位置x的差分前缀和即:(x+1)*sum1数组中p的前缀和-sum2数组中p的前缀和。

    ll query(ll x) //查询(差分数组的)区间前缀和
     { ll sum=0; for(int i=x;i>0;i-=i&-i) sum+=(x+1)*sum1[i]-sum2[i]; return sum; }

    <4> 二维 —— 单点修改,区间查询

    void add(ll x,ll y,ll k){ //【单点修改】
        for(int i=x;i<=n;i+=i&-i)
            for(int j=y;j<=m;j+=j&-j) c[i][j]+=k;
    } //【维护二维前缀和】
     
    ll query(ll x,ll y){ //【查询二维前缀和】
        ll sum=0; //即:从左上角的(1,1)到(x,y)的矩阵和
        for(int i=x;i>=1;i-=i&-i)
            for(int j=y;j>=1;j-=j&-j) sum+=c[i][j];
        return sum; //返回二维前缀和
    }

    <5> 二维 —— 区间修改,单点查询

    修改时:add(x,y,k),add(xx+1,yy+1,k),add(xx+1,y,-k),add(x,yy+1,-k);

    修改时用到了差分的思想,查询时直接 a[x][y]+query(x,y) 即可。

    (2)单调队列

    1、维护队首可行性,head++;

    2、维护队尾单调性,并插入当前元素;

    3、取出队头的最优解,进行DP转移。

    int head=1,tail=1; //【滑动窗口·区间max】
     
    q[1].x=a[1]; q[1].id=1; //初始点为1
     
    for(int i=2;i<=n;i++){ //从2开始循环
        
        while(head<=tail && q[head].id<i-m+1) head++; //id的作用:判断区间长度
        if(i>=m) printf("%d
    ",q[head].x); //每一次的队头都是当前段最大值
        
        while(head<=tail && q[tail].x<=a[i]) tail--;
        //↑↑新数比前几个大,前几个不可能再成为最大值(可能不止一个)
        q[++tail].x=a[i]; q[tail].id=i; //a[i]加入队尾
        
        //即:维护一个单调递减队列,如果后方有数更大,前面就全部删除。
    }

    (3)线段树

    struct SegmentTree{ int l,r,sum; }tree[4*N];
    
    void PushUp(int rt){ tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum; }
    
    void build(int l,int r,int rt){ //【建树】
        tree[rt].l=l; tree[rt].r=r; //建立标号与区间的关系
        if(l==r){ scanf("%d",&tree[rt].sum); return; } //叶子节点
        int mid=(l+r)/2; build(l,mid,rt<<1),build(mid+1,r,rt<<1|1);
        PushUp(rt); //将修改值向上传递
    }
    
    void add(int p,int rt){ //【单点修改】
        if(tree[rt].l==tree[rt].r){ tree[rt].sum+=y; return; } //叶子节点
        int mid=(tree[rt].l+tree[rt].r)>>1;
        if(p<=mid) add(p,rt<<1); else add(p,rt<<1|1);
        tree[rt].sum=tree[rt<<1].sum+tree[rt<<1|1].sum; //pushup
    }
    
    void query(int p,int rt){ //【单点查询】
        if(tree[rt].l==tree[rt].r){ ans=tree[rt].sum; return; } //叶子节点
        int mid=(tree[rt].l+tree[rt].r)>>1;
        if(p<=mid) query(p,rt<<1); else query(p,rt<<1|1);
    }
    
    void sum(int rt){ //【区间查询求和】
        if(tree[rt].l>=x&&tree[rt].r<=y) //区间完全包含
          { ans+=tree[rt].sum; return; }
        int mid=(tree[rt].l+tree[rt].r)>>1;
        if(x<=mid) sum(rt<<1); //区间部分重叠,递归左右
        if(y>=mid+1) sum(rt<<1|1);
    }

    (4)点分治

    主程序中: root=0; sum=f[0]=n; //一开始,root初始化为0,用于找重心 
                       getroot(1,0); solve(root); //从重心开始点分治

    int n,m,k,head[N],cnt; //head[]和cnt(=tot)
    
    int root,sum; //当前查询的根,当前递归的这棵树的大小 
    
    int vis[N]; //某一个点是否被当做根过 
    
    int sz[N]; //每个点下面子树的大小 
    int f[N]; //每个点为根时,最大子树大小 
    
    int dep[N]; //每个点的深度(此时是与根节点的距离) 
    int o[N]; //每个点的深度(继承dep[],用于排序,进而用于二分)
    
    int ans; //最终统计的答案 
    
    void getroot(int u,int fa){ //dfs求【重心】和【子树大小】
        sz[u]=1; f[u]=0;
        for(int i=head[u];i;i=e[i].nextt){
            int v=e[i].ver; if(v==fa||vis[v]) continue;
            getroot(v,u); sz[u]+=sz[v]; f[u]=max(f[u],sz[v]);
        } f[u]=max(f[u],sum-sz[u]); //注意:可能是另外一半的树
        if(f[u]<f[root]) root=u; //更新重心
    }
    
    void getdeep(int u,int fa){ //dfs求出与根节点的【距离dep】
        o[++cnt]=dep[u]; //用于排序
        for(int i=head[u];i;i=e[i].nextt){
            int v=e[i].ver; if(v==fa||vis[v]) continue;
            dep[v]=dep[u]+e[i].w; getdeep(v,u);
        }
    }
    
    int calc(int u,int d0){ 
    //↑↑↑此时以u为根节点,统计子树中符合条件的点对个数
        cnt=0; dep[u]=d0; getdeep(u,0);
        sort(o+1,o+cnt+1); //排序,便于二分
        int l=1,r=cnt,res=0;
        while(l<r){ 
            if(o[l]+o[r]<=k) res+=r-l,l++;
            else r--; //二分求符合条件的点对个数
        } return res;
    }
    
    void solve(int u){
        ans+=calc(u,0); vis[u]=1;
        //↑↑会产生非法路径(被u的某个子树完全包含,路径不能合并)
        for(int i=head[u];i;i=e[i].nextt){ //递归子树
            int v=e[i].ver; if(vis[v]) continue; //fa
            ans-=calc(v,e[i].w); //容斥原理去除非法答案
            //↑↑在处理子树时,将初始长度设为连接边长e[i].w;
            //这样做就相当于给子树的每个组合都加上了u—>..的路径。
            sum=sz[v]; root=0; //重设当前总树大小,寻找新的分治点
            getroot(v,0); solve(root); //递归新的分治点(重心)
        }
    }

    (5)分块&莫队

    https://www.cnblogs.com/FloraLOVERyuuji/p/10428197.html

    while(l>q[i].l) add(col[--l]);
    while(r<q[i].r) add(col[++r]);
    while(l<q[i].l) del(col[l++]);
    while(r>q[i].r) del(col[r--]);
    //↑↑q[i].l/r即正在处理的询问区间的两个端点
    int block=(int)sqrt(n); //分块
    for(int i=1;i<=n;i++) pos[i]=(i-1)/block+1;
    for(int i=1;i<=m;i++) reads(q[i].l),reads(q[i].r),q[i].id=i;
    sort(q+1,q+m+1,cmp); solve(); //离线,进行莫队算法
    sort(q+1,q+m+1,cmp_id); //复原询问的编号

    (6)树链剖分 / 平衡树 / 左偏树(可并堆) / 主席树 见上面

    【四、图论】

    (1)Tarjan缩点

    int dfn[N],low[N],stack[N],vis[N];
     
    int dfn_=0,top_=0,sum=0,col[N];
     
    //dfn序,栈中位置top,强连通个数sum,每点所属连通块编号col[i]
    
    for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i);
    
    void tarjan(int u){ //dfn_记录当前dfs序到达的数字
        
        dfn[u]=low[u]=++dfn_,vis[u]=1,stack[++top_]=u; //步骤一:初始化
        
        for(int i=head[u];i;i=e[i].nextt){ //步骤二:枚举连向点,递归更新
            if(!dfn[e[i].ver]) tarjan(e[i].ver),low[u]=min(low[u],low[e[i].ver]);
            else if(vis[e[i].ver]) low[u]=min(low[u],dfn[e[i].ver]); //这里写dfn或low都可以
        } //↑↑步骤三:已经到达过,判断是否在当前栈内(栈内都是当前情况下能相连的点)
        
        if(dfn[u]==low[u]){
            col[u]=++sum; vis[u]=0;
            while(stack[top_]!=u){ //u上方的节点是可以保留的
                col[stack[top_]]=sum;
                vis[stack[top_]]=0,top_--;
            } top_--; //col数组记录每个点所在连通块的编号
        }
    }
    
    int times[N],du[N]; //times数组/du数组记录每个强连通分量的大小/入度
     
    for(int u=1;u<=n;u++){
        for(int i=head[u];i;i=e[i].nextt)
            if(col[e[i].ver]!=col[u]) du[col[e[i].ver]]++;
        times[col[u]]++; //记录强连通分量大小
    }

    (2)拓扑排序

    queue<int>q; //给出n个顺序关系,问是否合法。
     
    bool tp_sort(){ //拓扑排序判环
        for(int i=1;i<=n;i++)
            if(rd[i]==0) q.push(i);
        while(!q.empty()){
            x=q.front(),q.pop(),cnt++;
            for(int i=head[x];i;i=e[i].nextt){
                rd[e[i].ver]--; //rd--,相当于‘删边’
                if(rd[e[i].ver]==0) q.push(e[i].ver);
            }
        } if(cnt==n) return true; return false;
    } //拓扑判环

    (3)差分约束

    • 给出一些形如x-y<=b不等式的约束,问你满足条件是否有解。

    方法:找适当的方式建边(一般是在x,y之间建立长度为b的边),转换成最短路问题。

    建边:1.b-a<=-c,w(b,a)=-c; 2.w(a,b)=c; 3.w(a,b)=w(b,a)=0。

    因为随便值为多少,所以从0向1~n每个点连边w[0,i]=0。用SPFA求最短路,出现环则No。

    (4)2-sat 问题 

    • 对于每个要求(a∨b),转换为 ( ¬a→b )∧(¬b→a ) ,
    • 即:「若 a 假则 b 必真,若 b 假则 a 必真」。
    • 然后按照箭头的方向建有向边,进行相关的缩点、拓扑排序操作。
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<cassert>
    #include<iostream>
    #include<algorithm>
    #include<queue>
    #include<vector>
    #include<map>
    #include<set>
    #include<deque>
    using namespace std;
    typedef long long ll;
    
    #define R register
    
    /*【p4782】2-sat问题
    有n个布尔变量x1~xn,另有m个需要满足的条件,
    每个条件的形式都是“xi为true/false或xj为true/false”。
    2-SAT问题的目标是给每个变量赋值使得所有条件得到满足。*/
    
    void reads(int &x){ //读入优化(正负整数)
        int f=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
        x*=f; //正负号
    }
    
    const int N=2500019,M=5000019;
    
    int n,m,dfn[N],low[N],stack[N],vis[N];
     
    int dfn_=0,top_=0,colnum=0,col[N];//siz[N];
    
    int head[N],tot=0,rd[N],okk=1;
    
    struct node{ int ver,nextt; }e[M];
    
    inline void add(R int x,R int y){ 
        e[++tot].nextt=head[x],e[tot].ver=y,head[x]=tot; 
    }
    
    inline void tarjan(int x){
        dfn[x]=low[x]=++dfn_,stack[++top_]=x,vis[x]=1;
        for(R int i=head[x];i;i=e[i].nextt){
            if(!dfn[e[i].ver]) 
          tarjan(e[i].ver),low[x]=min(low[x],low[e[i].ver]);
            else if(vis[e[i].ver]) low[x]=min(low[x],dfn[e[i].ver]);
        } if(dfn[x]==low[x]){
            col[x]=++colnum; vis[x]=0; //siz[colnum]++; 
            while(stack[top_]!=x){ //x上方的节点是可以保留的
                col[stack[top_]]=colnum; //siz[colnum]++;
                vis[stack[top_]]=0,top_--;
            } top_--; //col数组记录每个点所在连通块的编号
        }
    }
    
    int main(){
        int u,v,uw,vw; reads(n),reads(m);
        for(R int i=1;i<=m;i++){
            reads(u),reads(uw),reads(v),reads(vw);
            int notu=uw^1,notv=vw^1; //命题的否定
            add(u+notu*n,v+vw*n),add(v+notv*n,u+uw*n);//(非u,v),(非v,u)
        } for(R int i=1;i<=n*2;i++) if(!dfn[i]) tarjan(i);
        for(R int i=1;i<=n;i++) if(col[i]==col[i+n]){ okk=0; break; }
        if(okk){ printf("POSSIBLE
    "); //↓↓此情况下的真假
            for(int i=1;i<=n;i++) printf("%d ",col[i]>col[i+n]); 
        } else printf("IMPOSSIBLE
    "); return 0;
    }
    【p4782】2-sat问题

    (5)割点

    /*【p3225】矿场搭建 */
    
    //【标签】数学统计 + 分情况讨论 + 割点 + 无向图双连通分量
    
    /* 割点:在一个【无向图】中,如果有一个顶点集合,
            删除这个顶点集合以及这个集合中所有顶点相关联的边以后,
            图的连通分量增多,就称这个点集为割点【集合】。  */
    
    // 双连通分量:无向图中的强连通分量,去掉任意一个节点都不会改变连通性
    
    /*【tarjan算法求割点】
    1.判断根节点:计算其子树数量,如果有2棵即以上的子树,就是割点。
    2.回顾一下low[i]的定义:u及其子树中的点,能够连向到的dfn最小的点的dfn值。
    2.对于边(u,v),如果low[v]>=dfn[u],那么low[v]没有返祖边,此时u就是割点。*/
    
    /*【思路】统计每个双连通分量里的割点的个数,并分情况讨论。
    1.若该连通分量里割点>=2,可以通过其他割点跑到其他的双连通分量里。
    2.若该连通分量里割点=1,所以要在任意一个非割点处建一个出口。
    3.若该连通分量里割点=0,说明它与外界完全不连通,需要建两个出口以防万一。*/
    const int N=519;
    
    int n,m,tot=0,head[N],dfn[N],low[N],cut[N],vis[N],root;
    
    int dfn_=0,sum,cnt,cut_num; //连通块数sum,当前连通块节点数、割点数
    
    int kase=0; ll ans1,ans2; //选点数,方案数
    
    struct node{ int ver,nextt; }e[N*2];
    
    void add(int u,int v){ e[++tot].ver=v,e[tot].nextt=head[u],head[u]=tot; }
    
    void tarjan(int u,int fa){ //【tarjan求割点】
        
        dfn[u]=low[u]=++dfn_; int child=0;
        //fa是子树的根节点,如果儿子数>=2则是割点
       
        for(int i=head[u];i;i=e[i].nextt){
          if(!dfn[e[i].ver]){ 
            tarjan(e[i].ver,u),low[u]=min(low[u],low[e[i].ver]);
            if(low[e[i].ver]>=dfn[u]&&u!=root) cut[u]=true;
            if(u==root) child++; //root节点的儿子数++
          } low[u]=min(low[u],dfn[e[i].ver]);
        } if(child>=2&&u==root) cut[u]=true;
    }
    
    void dfs(int u){ //遍历每个连通块
        vis[u]=sum; cnt++; //节点数cnt++
        for(int i=head[u];i;i=e[i].nextt){
            if(cut[e[i].ver]&&vis[e[i].ver]!=sum) 
                cut_num++,vis[e[i].ver]=sum; //e[i].ver是割点
            if(!vis[e[i].ver]) dfs(e[i].ver); //非割点
        }
    }
    
    void init(){
        memset(head,0,sizeof(head));
        memset(dfn,0,sizeof(dfn));
        memset(low,0,sizeof(low));
        memset(cut,0,sizeof(cut));
        memset(vis,0,sizeof(vis));
        dfn_=n=tot=sum=ans1=0,ans2=1;
    }
    
    int main(/*hs_love_wjy*/){
        while(scanf("%d",&m)==1&&m){
            init(); //日常清零
            for(int i=1,u,v;i<=m;i++){
                reads(u),reads(v),n=max(n,max(u,v));
                add(u,v),add(v,u); //↑↑记录点的总个数n
            } for(int i=1;i<=n;i++) if(!dfn[i]) root=i,tarjan(i,0);
            for(int i=1;i<=n;i++) if(!vis[i]&&!cut[i]){
                sum++; cnt=cut_num=0; dfs(i); //到达一个新的连通块
                if(!cut_num) ans1+=2,ans2*=cnt*(cnt-1)/2; //建两个
                if(cut_num==1) ans1++,ans2*=cnt; //建一个
            } printf("Case %d: %lld %lld
    ",++kase,ans1,ans2);
        }
    }

    【五、网络流】

    (1)最大流 / 最小割

     网络流最小割  最小割 = 最大流 。注意拆点操作。

    (有正负权值时)最大权闭合子图 = s连的正权值之和 - min_cut 。

    int s,t,tot=1,n,m,ans,head[N],dep[N],cur[N]; //s为源点,t为汇点 
    
    struct node{ int nextt,ver,w; }e[M];
    
    void add(int x,int y,int z)
     { e[++tot].ver=y,e[tot].nextt=head[x],e[tot].w=z,head[x]=tot; }
    
    bool bfs(){
        memset(dep,0,sizeof(dep)); //dep记录深度 
        memcpy(cur,head,sizeof(head));
        queue<int> q; while(!q.empty()) q.pop();
        dep[s]=1; q.push(s);
        while(!q.empty()){
            int u=q.front(); q.pop();
            for(int i=head[u];i;i=e[i].nextt)
                if((e[i].w>0)&&(dep[e[i].ver]==0)) //分层 
                    dep[e[i].ver]=dep[u]+1,q.push(e[i].ver);
        } if(dep[t]!=0) return 1;
          else return 0; //此时不存在分层图也不存在增广路
    }
    
    int dfs(int u,int lastt){ int cnt=0;
        if(u==t) return lastt; //lastt:此点还剩余的流量
        for(int i=cur[u];i&&cnt<lastt;i=e[i].nextt){ 
            cur[u]=i; //当前弧优化 
            if((dep[e[i].ver]==dep[u]+1)&&(e[i].w!=0)){
                int f=dfs(e[i].ver,min(lastt-cnt,e[i].w)); 
                if(f>0){ e[i].w-=f,e[i^1].w+=f; cnt+=f; } }
        } if(cnt<lastt) dep[u]=-1; return cnt;
    }
    
    void dinic(){ ans=0; while(bfs()) ans+=dfs(s,2e9); }

    (2)最小费用最大流

    struct edge{ ll ver,nextt,flow,cost; }e[2*N];
    
    ll tot=-1,n,m,S,T,maxf=0,minc=0;
    
    ll flow[N],head[N],dist[N],inq[N],pre[N],lastt[N];
    
    void add(ll a,ll b,ll f,ll c)
    { e[++tot].nextt=head[a],head[a]=tot,
      e[tot].ver=b,e[tot].flow=f,e[tot].cost=c; } 
    
    bool spfa(ll S,ll T){
        queue<ll> q;
        memset(inq,0,sizeof(inq));
        memset(flow,0x7f,sizeof(flow));
        memset(dist,0x7f,sizeof(dist));
        q.push(S),dist[S]=0,pre[T]=-1,inq[S]=1;
        while(!q.empty()){
            ll x=q.front(); q.pop(); inq[x]=0;
            for(ll i=head[x];i!=-1;i=e[i].nextt){
                if(e[i].flow>0&&dist[e[i].ver]>dist[x]+e[i].cost){
                    dist[e[i].ver]=dist[x]+e[i].cost;
                    pre[e[i].ver]=x,lastt[e[i].ver]=i;
                    flow[e[i].ver]=min(flow[x],e[i].flow);
                    if(!inq[e[i].ver])
                        q.push(e[i].ver),inq[e[i].ver]=1;
                }
            }
        } return pre[T]!=-1;
    }
    
    void mcmf(){
        while(spfa(S,T)){
            ll now=T; //↓↓最小费用最大流
            maxf+=flow[T],minc+=dist[T]*flow[T];
            while(now!=S){ //↓↓正边流量-,反边流量+
                e[lastt[now]].flow-=flow[T];
                e[lastt[now]^1].flow+=flow[T]; 
                //↑↑利用xor1“成对储存”的性质
                now=pre[now]; //维护前向边last,前向点pre
            }
        }
    }
    
    //注意:tot=1,memset(head,-1,sizeof(head)); 

    【六、动态规划】

    (1)树形DP

    https://www.cnblogs.com/FloraLOVERyuuji/p/10560021.html

    (2)基环树DP

     https://www.cnblogs.com/FloraLOVERyuuji/p/10419674.html

    (3)状压DP

    https://www.cnblogs.com/FloraLOVERyuuji/p/10601568.html

    (4)矩阵优化DP

    https://www.cnblogs.com/FloraLOVERyuuji/p/10512899.html

    (5)斜率优化DP

    • 对于每个斜率方程 (Y(j2)-Y(j1))/(X(j2)-X(j1))

        1.将数据进行预处理(求sum等操作),优化序列。

        2.写状态转移方程,如果是二维,要使用二维单调队列。

        3.推导不等式,化成斜率的一般式,一般使用化除为乘。

        4.从而得到X,Y的定义式,用double类型表示出来。

        5.建立一个类似优先队列的斜率单调队列。

        6.维护头尾可行性以及斜率单调性,队头为最优答案。

    【七、数论】

    (1)欧拉函数

    https://www.cnblogs.com/FloraLOVERyuuji/p/10423022.html

    int euler(int x){
        int ans=x;
        for(int i=2;i*i<=x;i++)
            if(x%i==0){
                ans=ans/i*(i-1);
                while(x%i==0) x/=i;
        } if(x>1) ans=ans/x*(x-1);
        return ans; //返回ϕ(x)的值
    } // ϕ(n) = n∗∏(i=1~k)((ai-1)/ai);
    int phi[MAXN],vis[MAXN],prime[MAXN],tot=0;
    
    void GetPhi(int n){ //【phi线性筛法】
        phi[1]=1; //特例:ϕ(1)=1;
        for(int i=2;i<=n;i++){
            if(!vis[i]) prime[++tot]=i,phi[i]=i-1; //i是素数(1)
            for(int j=1;j<=tot&&i*prime[j]<=n;j++){ //枚举“i*质数”
                vis[i*prime[j]]=1; //标记“i*质数”为合数
                if(i%prime[j]==0) //(3)注意:剪枝,赋值后直接break;
                 {phi[i*prime[j]]=phi[i]*prime[j];break;}
                else phi[i*prime[j]]=phi[i]*(prime[j]-1); } //(2)
        } for(int i=1;i<=n;i++) cout<<phi[i]<<endl;
    }
    • 原根个数为φ(φ(m));由于素数的φ(m)=m-1,所以素数的原根=φ(m-1)。

    (2)欧拉定理

    欧拉定理

    扩展欧拉定理:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #include<string>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    // p5091 【模板】 扩展欧拉定理 // 求a^b mod m 
    
    //【扩展欧拉定理】 b≥φ(m)时,a^b≡a^((bmodφ(m))+φ(m)) mod m ;
    
    int a,b,m,ans=1; bool flag;
    
    int euler(int x){
        int ans=x;
           for(int i=2;i*i<=x;i++)
            if(x%i==0){
                ans=ans/i*(i-1);
                while(x%i==0) x/=i;
        } if(x>1) ans=ans/x*(x-1);
        return ans; //返回ϕ(x)的值
    }
    
    int main(){
        char c; scanf("%d%d",&a,&m); int phi=euler(m);
        while(!isdigit(c=getchar())); //边读入b边取模
            for(;isdigit(c);c=getchar()){
                b=b*10+c-'0'; if(b>=phi) flag=true,b%=phi;
        } if(flag) b+=phi; //只有b>=phi时,公式才成立(否则不用+φ(m))
        for(int i=20;i>=0;i--){ //ksm求a^((bmodφ(m))+φ(m))
            ans=1ll*ans*ans%m;
            if(b&(1<<i)) ans=1ll*ans*a%m;
        } cout<<ans<<endl; return 0;
    }
    p5091 【模板】 扩展欧拉定理 // 求a^b mod m

    (3)卢卡斯定理

    • 公式:Lucas(C(n,m),p)=Lucas(C(n%p,m%p),p)*Lucas(C(n/p,m/p),p)
    long long inv[100010],kk[100010],p;
    
    long long lucas(int x,int y){
        if(x<y) return 0; //无法构成组合数,返回答案为0
        if(x<p) return kk[x]*inv[y]*inv[x-y]%p; //x的阶乘*(y!%p的逆元)*((x-y)!%p的逆元)
        else return lucas(x/p,y/p)*lucas(x%p,y%p)%p;
    }
    
    int main(){
        int T,n,m; scanf("%d",&T); while(T--){
            scanf("%d%d%lld",&n,&m,&p);
            inv[0]=inv[1]=kk[0]=kk[1]=1; //阶乘数组&&逆元数组初始化
            for(int i=2;i<=n+m;i++) kk[i]=kk[i-1]*i%p;
            for(int i=2;i<=n+m;i++) inv[i]=(p-p/i)*inv[p%i]%p;
            for(int i=2;i<=n+m;i++) inv[i]=inv[i-1]*inv[i]%p; //逆元的阶乘 等于 k!%p的逆元
            printf("%lld
    ",lucas(n+m,m)); //调用卢卡斯函数
        }
    }

    (4)莫比乌斯反演

    https://www.cnblogs.com/FloraLOVERyuuji/p/10539217.html

    void get_mu(int n){
        mu[1]=1; for(int i=2;i<=n;i++){
            if(!vis[i]) primes[++cnt]=i,mu[i]=-1;
            for(int j=1;j<=cnt&&primes[j]*i<=n;j++){
                vis[primes[j]*i]=1;
                if(i%primes[j]==0) break;
                else mu[i*primes[j]]=-mu[i];
            }
        }
     }

    (5)线性基

    // 最大异或和:给定n个整数(数字可能重复),
    //    在这些数中选取任意个,使得他们的异或和最大。
    
    ll base[5019];
    
    int main(){
        ll n,tmp; scanf("%lld",&n);
        for(int i=1;i<=n;i++){ scanf("%lld",&tmp);
          for(int j=50;j>=0;--j) if(tmp&(1LL<<j))
           { if(!base[j]) base[j]=tmp; tmp^=base[j]; } }
        ll ans=0; for(int i=50;i>=0;i--)
            if(ans<(ans^base[i])) ans^=base[i];
        printf("%lld
    ",ans); return 0;
    }

    (6)矩阵乘法

    struct Mat{ ll m[9][9]; }a,dp; //a是原始矩阵,dp是构造的矩阵
    
    Mat mat_mul(Mat x,Mat y){ //矩阵乘:x*y=c
        Mat c; memset(c.m,0,sizeof(c.m)); //【注意矩阵的‘清空’】
        for(ll i=1;i<=3;i++) for(ll j=1;j<=3;j++) for(ll k=1;k<=3;k++)
            c.m[i][j]=(c.m[i][j]%mod+x.m[i][k]*y.m[k][j]%mod)%mod; return c; }
    
    Mat mat_pow(Mat x,ll y) //矩阵快速幂:ans*x^y
     {  Mat ans; memset(ans.m,0,sizeof(ans.m)); //记得要随时清空
        for(ll p=1;p<=3;p++) ans.m[p][p]=1; //初始化为单位矩阵【清空】
        while(y){ if(y&1) ans=mat_mul(ans,x); x=mat_mul(x,x); y>>=1; } return ans; }

    (7)Matrix-Tree定理

    https://www.cnblogs.com/FloraLOVERyuuji/p/10524226.html

    int Gauss(){ //高斯消元
      int ans=1; for(int i=1;i<=tot;i++){
        for(int j=i+1;j<=tot;j++) //tot是总点数
              while(f[j][i]){ int t=f[i][i]/f[j][i];
            for(int k=i;k<=tot;k++)
                f[i][k]=(f[i][k]-t*f[j][k]%mod+mod)%mod,
                swap(f[i][k],f[j][k]); ans=-ans; //辗转相除法
          } ans=(ans*f[i][i])%mod;
      } return (ans+mod)%mod; //注意ans可能为负数
    }

    (8)FFT

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<string>
    #include<queue>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    
    //【p1919】A*B Problem
    
    void reads(int &x){ //读入优化(正负整数)
        int fa=1;x=0;char s=getchar();
        while(s<'0'||s>'9'){if(s=='-')fa=-1;s=getchar();}
        while(s>='0'&&s<='9'){x=(x<<3)+(x<<1)+s-'0';s=getchar();}
        x*=fa; //正负号
    }
    
    const int N=1000019;
    
    struct complex{ //复数
        double x,y;
        complex(){} //复数的相关运算
        complex(double x,double y){this->x=x,this->y=y;}
        complex friend operator +(complex n1,complex n2)
          {return complex(n1.x+n2.x,n1.y+n2.y);}
        complex friend operator -(complex n1,complex n2)
          {return complex(n1.x-n2.x,n1.y-n2.y);}
        complex friend operator *(complex n1,complex n2)
          {return complex(n1.x*n2.x-n1.y*n2.y,n1.x*n2.y+n1.y*n2.x);}
    }a[N],b[N],tmpx,tmpy,wn,w;
    
    const double pi=3.1415926535897632;
    
    int n,m,turn[N],len=1,L=-1;
    
    char s1[N],s2[N]; int aa=0,bb=0,ans[N];
    
    void FFT(complex *a,int typ){
        for(int i=0;i<len;i++)
            if(i<turn[i]) swap(a[i],a[turn[i]]);
        for(int l=1;l<len;l<<=1){
            wn=complex(cos(pi/l),typ*sin(pi/l));
            for(int p=0;p<len;p+=(l<<1)){
                w=complex(1,0); //a+b*i
                for(int i=p;i<p+l;i++,w=w*wn){
                    tmpx=a[i],tmpy=w*a[i+l];
                    a[i]=tmpx+tmpy,a[i+l]=tmpx-tmpy;
                } //↑↑用“蝴蝶操作”优化
            }
        }
    }
    
    int main(){ //把每一位看成一个系数,最后再整合
    
        reads(n); scanf("%s%s",s1,s2);
        for(int i=n-1;i>=0;i--) a[aa++].x=s1[i]-48;
        for(int i=n-1;i>=0;i--) b[bb++].x=s2[i]-48;
        
        while(len<(n+n)) len<<=1,L++;
        
        for(int i=0;i<=len;i++) turn[i]=(turn[i>>1]>>1)|((i&1)<<L);
        //↑↑位逆序替换,就找到了对应的turn位置
         
        /*  实现思路:系数表示法—>点值表示法—>系数表示法。
            后面的1表示要进行的变换是什么类型。
            1表示从系数变为点值,-1表示从点值变为系数。 */
    
        FFT(a,1),FFT(b,1); //从系数变为点值
        for(int i=0;i<=len;i++) a[i]=a[i]*b[i]; //记录乘积答案
    
        FFT(a,-1); //把乘积答案转化为各位置的系数
        for(int i=0;i<=len;i++){
            ans[i]+=(int)(a[i].x/len+0.5); //系数整合为大整数 
            if(ans[i]>=10) ans[i+1]+=ans[i]/10,ans[i]%=10,
                len+=(i==len); //判断是否要多一位
        } while(!ans[len]&&len>=1) len--; //删除前导零
    
        len++; while(--len>=0) cout<<ans[len]; //输出答案
    }
    【p1919】A*B Problem

    【八、计算几何】

    (二维凸包 / 旋转卡壳  / 半平面交)

    struct point{ double x,y; }a[10019];
    
    int sta[10019],top,n; double ans=0.0;
    
    double cross(point p0,point p1,point p2) //计算向量叉积
     { return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x); }
    
    double dis(point p1,point p2)  //计算点p1p2的距离
     { return sqrt((double)(p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y)); }
    
    bool cmp(point p1,point p2){ //进行极角排序
        double tmp=cross(a[0],p1,p2); if(tmp>0) return true;
        else if(tmp==0&&dis(a[0],p1)<dis(a[0],p2)) return true;
        else return false; //↑↑若角度相同,则距离小的在前面
    }
    
    void init(){ //输入,并把最左下方的点放在a[0],进行极角排序。 
        point p0; scanf("%lf%lf",&a[0].x,&a[0].y);
        p0.x=a[0].x; p0.y=a[0].y; int k=0;
        for(int i=1;i<n;i++){ scanf("%lf%lf",&a[i].x,&a[i].y);
            if( (p0.y>a[i].y) || ((p0.y==a[i].y)&&(p0.x>a[i].x)) )
                p0.x=a[i].x,p0.y=a[i].y,k=i; //寻找左下角的点
        } a[k]=a[0],a[0]=p0; //把原来0位置的点放到k位置(互换位置)
        sort(a+1,a+n,cmp); //除去0号点,其余n-1个点进行极角排序
    }     
    
    void graham(){ //极角排序法求凸包
        if(n==1) top=0,sta[0]=0;
        if(n==2) top=1,sta[0]=0,sta[1]=1;
        if(n>2){ top=1,sta[0]=0,sta[1]=1;
          for(int i=2;i<n;i++){
            while(top>0&&cross(a[sta[top-1]],a[sta[top]],a[i])<=0) top--;
            top++; sta[top]=i; } //每加入新的点,判断要出栈的凹点,并将新点入栈
        }    
    }    
    
    int main(){
        scanf("%d",&n); init(); graham(); //输入+极角排序+求凸包
        for(int i=0;i<top;i++) ans+=dis(a[sta[i]],a[sta[i+1]]);
        ans+=dis(a[sta[0]],a[sta[top]]); printf("%.2lf
    ",ans); //凸包总周长
    }

                                                                        ——时间划过风的轨迹,那个少年,还在等你

  • 相关阅读:
    27 Spring Cloud Feign整合Hystrix实现容错处理
    26 Spring Cloud使用Hystrix实现容错处理
    25 Spring Cloud Hystrix缓存与合并请求
    24 Spring Cloud Hystrix资源隔离策略(线程、信号量)
    23 Spring Cloud Hystrix(熔断器)介绍及使用
    22 Spring Cloud Feign的自定义配置及使用
    21 Spring Cloud使用Feign调用服务接口
    20 Spring Cloud Ribbon配置详解
    19 Spring Cloud Ribbon自定义负载均衡策略
    18 Spring Cloud Ribbon负载均衡策略介绍
  • 原文地址:https://www.cnblogs.com/FloraLOVERyuuji/p/10638189.html
Copyright © 2011-2022 走看看