数据结构+图论模板

1.LCA(两个节点的最近公共祖先)

LCA_Tarjan(并查集)(时间复杂度O(n+q))
#include<bits/stdc++.h>
using namespace std;
const int N=40000+5;
struct Edge{
    int cnt,x[N],y[N],z[N],nxt[N],fst[N];
    void set(){
 cnt=0;memset(x,0,sizeof x);memset(y,0,sizeof y);memset(z,0,sizeof z);
memset(nxt,0,sizeof nxt);memset(fst,0,sizeof fst);
    }
    void add(int a,int b,int c){
      x[++cnt]=a;y[cnt]=b;z[cnt]=c;nxt[cnt]=fst[a];fst[a]=cnt;
    }
}e,q;
int T,n,m,from,to,dist,in[N],rt,dis[N],fa[N],ans[N];
bool vis[N];
void dfs(int rt){
    for (int i=e.fst[rt];i;i=e.nxt[i]){
        dis[e.y[i]]=dis[rt]+e.z[i];
        dfs(e.y[i]);
    }
}
int getf(int k){
    return fa[k]==k?k:fa[k]=getf(fa[k]);
}
void LCA(int rt){
    for (int i=e.fst[rt];i;i=e.nxt[i]){
        LCA(e.y[i]);
        fa[getf(e.y[i])]=rt;//直接父代
    }
    vis[rt]=1;
    for (int i=q.fst[rt];i;i=q.nxt[i])
        if (vis[q.y[i]]&&!ans[q.z[i]])
            ans[q.z[i]]=dis[q.y[i]]+dis[rt]-2*dis[getf(q.y[i])];
}
int main(){
    scanf("%d",&T);
    while (T--){
        q.set(),e.set();memset(in,0,sizeof in);
memset(vis,0,sizeof vis);memset(ans,0,sizeof ans);
        scanf("%d%d",&n,&m);
        for (int i=1;i<n;i++)
        scanf("%d%d%d",&from,&to,&dist),e.add(from,to,dist),in[to]++;
        for (int i=1;i<=m;i++)
        scanf("%d%d",&from,&to),q.add(from,to,i),q.add(to,from,i);
        rt=0;
        for(int i=1;i<=n&&rt==0;i++)
            if(in[i]==0) rt=i;
        dis[rt]=0;
        dfs(rt);//更新权值
        for(int i=1;i<=n;i++) fa[i]=i;
        LCA(rt);//找公共祖父
        for(int i=1;i<=m;i++)  printf("%d\n",ans[i]);
    }
    return 0;
}


2.LCA_倍增 时间和空间复杂度分别是O((n+q)log n)O(n log n)

using namespace std;
const int N=10000+5;
vector <int> son[N];
int T,n,depth[N],fa[N][17],in[N],a,b;
void dfs(int prev,int rt){
    depth[rt]=depth[prev]+1;
    fa[rt][0]=prev;
    for (int i=1;(1<<i)<=depth[rt];i++)
        fa[rt][i]=fa[fa[rt][i-1]][i-1];
    for (int i=0;i<son[rt].size();i++)
        dfs(rt,son[rt][i]);
}
int LCA(int a,int b){
    if (depth[a]>depth[b])
        swap(a,b);
    for (int i=depth[b]-depth[a],j=0;i>0;i>>=1,j++)
        if (i&1)
            b=fa[b][j];
    if (a==b)
        return a;
    int k;
    for (k=0;(1<<k)<=depth[a];k++);
    for (;k>=0;k--)
        if ((1<<k)<=depth[a]&&fa[a][k]!=fa[b][k])
            a=fa[a][k],b=fa[b][k];
    return fa[a][0];
}
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
            son[i].clear();
        memset(in,0,sizeof in);
        for (int i=1;i<n;i++){
            scanf("%d%d",&a,&b);
            son[a].push_back(b);
            in[b]++;
        }
        depth[0]=-1;
        int rt=0;
        for (int i=1;i<=n&&rt==0;i++)
            if (in[i]==0)
                rt=i;
        dfs(0,rt);
        scanf("%d%d",&a,&b);
        printf("%d\n",LCA(a,b));
    }
    return 0;
}

3.kmp算法

void getFail(char *P,int *f)//P模板串
{
    f[1]=f[0]=0;
    int m=strlen(P);
    for(int i=1,j;i<m;i++)
    {
        j=f[i];
        while(j&&P[i]!=P[j])
            j=f[j];
        f[i+1]=P[i]==P[j]?j+1:0;
    }
    for(int i=1;i<m;i++)
        if(P[i+1]==P[f[i+1]])
            f[i+1]=f[f[i+1]];
}

void find(char *T,char *P,int *f)//P子串
{
    int n=strlen(T);
    int m=strlen(P);
    int j=0;
    for(int i=0;i<n;i++)
    {
        while(j && T[i]!=P[j]) j=f[j];
        if(T[i]==P[j]) j++;
        if(j==m) cnt++;//i-m+1: 下标从0开始,能够匹配的初始位置。
    }
}

4.回文串最长回文串O(n) manacher算法

#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
const int inf = 1000000000;
int p[N*2],s[N*2],n;

void Manacher() {
    int id = 0, maxlen = 0;
    for (int i = n;i >= 0;--i) {
        s[i + i + 2] = s[i];
        s[i + i + 1] = -1;
   }
    s[0] = -2;
    for (int i = 2;i < 2 * n + 1;++i) {
        if (p[id] + id > i)p[i] = min(p[2 * id - i], p[id] + id - i);
        else p[i] = 1;
        while (s[i - p[i]] == s[i + p[i]]) ++p[i];
        if (id + p[id] < i + p[i])id = i;
        if (maxlen < p[i])maxlen = p[i];
    }
    //cout << maxlen - 1 << endl;
}
int main()
{
    int t,i,j,x,Max,k=1,c;
    scanf("%d",&t);
    while(t--)
    {
        Max=0;
        memset(p,0,sizeof(p));
        scanf("%d",&n);
        for(i=0;i<n;i++)  scanf("%d",&s[i]);
        Manacher();
        for(i=3;i<2 * n + 1;i+=2)
            if(p[i]-1>Max)
            {
                c=p[i]-1;
                while(c>Max&&p[i+c]<c)
                    c--;
                Max=Max>c?Max:c;
            }
        printf("Case #%d: %d\n",k++,Max/2*3);
    }
    return 0;

} 

5.RMQ(区间问题处理,O(1)查询,适用于查询次数多的情况)

#include<cstdio>
using namespace std;
const int MAXN=500000+100;
int a[MAXN],b[MAXN];
long long merge_sort(int *a,int *b,int i,int j)//归并排序并返回逆序值,a为待排序的数组,b为辅助空间

{
    if(i==j)return 0;
    long long ans=0;//逆序值
    int mid= (i+j)/2;
    ans+=merge_sort(a,b,i,mid);//获取左边的逆序值
    ans+=merge_sort(a,b,mid+1,j);//获取右边的逆序值
    int p=i,q=mid+1,k=i;
    while(p<=mid||q<=j)//获取左边与右边关联的逆序值
    {
        if(p>mid || (q<=j&&a[p]>a[q]))
        {
            b[k++]=a[q++];
            ans+=mid-p+1;
        }
        else
        {
            b[k++]=a[p++];
        }
    }
    for(int k=i;k<=j;k++)a[k]=b[k];
    return  ans;
}
int main()
{
    long long ans;
    int n;
    while(scanf("%d",&n)==1&&n)
    {
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        ans = merge_sort(a,b,0,n-1);
        printf("%I64d\n",ans);
    }
    return 0;
}

6.有向环 拓扑排序

/*  3 2   A<B   B<A  */
#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f;
int a[30][30],f[30],ans[30],n,m;
int query(int x) {
    int in[30],k=0,num=0,q,zz=1;
    for(int i=1;i<=n;i++) in[i]=f[i];
        for(int i=1;i<=n;i++) { k=0;
            for(int j=1;j<=n;j++) { if(in[j]==0) {q=j;k++;} }
             if(k==0) return 0;
             if(k>1) zz=-1;
             ans[num++]=q;in[q]--;
   for(int s=1;s<=n;s++) { if(a[q][s]) in[s]--; }
        }
        return zz;
}
int main()
{	int x,y;  char ch[5];
    while(~scanf("%d%d",&n,&m)){ int ok=0;
        if(n==0&&m==0) break;
        memset(f,0,sizeof(f)); memset(a,0,sizeof(a));
        for(int u=1;u<=m;u++) {
            scanf("%s",ch);
if(ok) continue;
             x=ch[0]-'A'+1; y=ch[2]-'A'+1;
            if(a[x][y]==0) {a[x][y]=1; f[y]++;}
            int s=query(n);
            if(s==0)  {//某一个后产生矛盾
       ok=1;  printf("Inconsistency found after %d relations.\n",u);
            }
            if(s==1) {//某一个关系后排好了大小关系
   ok=1;  printf("Sorted sequence determined after %d relations: ",u);
                for(int i=0;i<n;i++)  cout<<(char)('A'+ans[i]-1);
                cout<<"."<<endl;
            }
        }//不能确定大小关系
 if(ok==0){printf("Sorted sequence cannot be determined.\n");}
    }
}

7.欧拉回路

  1. PS:)棋盘问题 

参考论文

n*m个点,一笔画,(1,1)到(n,m),黑白相邻染色,只能不同色走,n,m都是偶数时会是始终点同色,倒置多另一种颜色,所以要建另一种颜色的线,消去多的颜色点数,即可以一笔走到

n*m个点,走回路(1,1)走遍所有点回到(1,1),n*m是偶数时走n*m步,n*m是奇数,只能黑到白,相当于起终点颜色不同,又因为奇数个点,所以颜色数量差为1,要有多的颜色的点之间的连线,以减少多出来的点,构成回路!

     2.一笔画

#include<bits/stdc++.h>
#define MAXN 1005 
using namespace std;
int N,M,a,b,pre[MAXN],in[MAXN];
vector<int>v;
int fun(int x) {
    if(x==pre[x]) return x;
    return pre[x]=fun(pre[x]);
}
void change(int x,int y) {
    int c=fun(x); int v=fun(y);
    if(c==v) return;
    pre[x]=y;
}
int main()
{
    while(~scanf("%d",&N)) {
        if(N==0) break;
        for(int i=1;i<=N;i++)
            pre[i]=i;
        memset(in,0,sizeof(in));
        scanf("%d",&M);
        for(int i=1;i<=M;i++) {
            scanf("%d%d",&a,&b);
            change(a,b);
            in[a]++;
            in[b]++;
        }
        int ok=1,z=fun(1);
        for(int i=1;i<=N;i++) {
                if(in[i]&1) ok=0;
                if(fun(i)!=z) ok=0;
            }
            cout<<ok<<endl;//欧拉回路存在则输出1,否则输出0.
    }
}

   3.欧拉回路:输出点轨迹

#include<bits/stdc++.h>
using namespace std;
#define MAX 20005
int look[MAX*5],n,num,l;
int S[MAX*10];
struct AA{int x,id;}aa;
vector<AA>v[MAX];
int dfs(int x){
    for(int i=0;i<v[x].size();i++){
        if(look[v[x][i].id]==0) {
    look[v[x][i].id]=1;dfs(v[x][i].x);S[num++]=v[x][i].x;
        }
    }
}
int main()
{
    int x,y,n,m;
    while(~scanf("%d%d",&n,&m))
    {num=0;
     memset(S,0,sizeof(S));memset(look,0,sizeof(look));
    for(int i=1;i<=MAX;i++)v[i].clear();
      while(m--){
       scanf("%d%d",&x,&y);
       aa.x=y;aa.id=(m+1)*2;v[x].push_back(aa);
       aa.x=x;aa.id=(m+1)*2-1;v[y].push_back(aa);
        }
    int ok=0;dfs(1);cout<<"1";
        while(num--){cout<<" "<<S[num];}
         cout<<endl;
    }
}

 

    4.欧拉回路例题思路

题意:有n个点,m条边,人们希望走完所有的路,且每条道路只能走一遍。至少要将人们分成几组。

解题思路:先用并查集求出所有的连通块,然后判断每个连通块内点的度数,如果有奇数点则需要的组数ans+=奇数点/2;反之,所需组数ans+=1。注意:如果遇到孤立点即度数为0的点则不用算进去,因为没有跟他相连的边。

 

  7.强连通

  1. 强连通分支 
    问题A:入度为0的点的个数
    问题B:需要添加边的数量,使图强连通
    #include<bits/stdc++.h>
    using namespace std;
    vector<int>v[105];
    stack<int>S;
    int look[105],low[105],pre[105],ok[105],num,ans[105],k;
    int dfs(int x) { S.push(x); low[x]=pre[x]=++num;
        for(int i=0;i<v[x].size();i++){
    if(!pre[v[x][i]]){dfs(v[x][i]); low[x]=min(low[x],low[v[x][i]]); }
     else if(!ans[v[x][i]])low[x]=min(low[x],pre[v[x][i]]);
        }
        if(low[x]==pre[x]){ k++;
            while(1){
      	int z=S.top();S.pop();
     	ans[z]=k; if(z==x) break;
            }
        }
    }
    int main()
    {
        int N,i,j,x;scanf("%d",&N);
        for(i=1;i<=N;i++) {
         while(scanf("%d",&x)) {
                if(x==0) break; v[i].push_back(x);
            }
        }
        for(i=1;i<=N;i++){if(!pre[i]) dfs(i); }
        int in[105],out[105],a=0,b=0;
        memset(in,0,sizeof(in));memset(out,0,sizeof(out));
        for(i=1;i<=N;i++)
        for(j=0;j<v[i].size();j++){
     if(ans[i]!=ans[v[i][j]]) in[ans[v[i][j]]]=out[ans[i]]=1;}
        if(k==1) printf("1\n0\n");
        else{ for(int i=1;i<=k;i++){if(in[i]==0) a++; if(out[i]==0) b++;}
            printf("%d\n%d\n",a,max(a,b));
        }
    }

     

  2. 强连通分量缩点
#include<bits/stdc++.h>
using namespace std;  
#define maxn 100005
struct AA{int v,next;}pos[maxn];
int f[maxn],low[maxn],dfn[maxn],stackk[maxn];//最小父代,时间戳,栈
int mm[maxn],in[maxn],look[maxn],ans[maxn],cc[maxn];//新图入度,新图的点位,,新图的点对应的原强连通图内最小的点
int aa,kk,op,num,zz,n,m;
int dfs(int rt){int v;
  		 dfn[rt]=++kk; low[rt]=kk;look[rt]=1;stackk[++zz]=rt;
      	for(int i=f[rt];i!=-1;i=pos[i].next){v=pos[i].v;
   	  if(!dfn[v]){ dfs(v);low[rt]=min(low[rt],low[v]);}
   	  else{if(look[v]){low[rt]=min(low[rt],low[v]);}}
 	  }
 	  if(dfn[rt]==low[rt]){
 		 in[rt]=++aa;cc[aa]=rt;look[rt]=0;
       while(stackk[zz]!=rt){ in[stackk[zz]]=aa;
cc[aa]=min(cc[aa],stackk[zz]);look[stackk[zz--]]=0;
       }zz--;
  	 }return 0;
}
int main()
{int u,v;
   cin>>n>>m; 
   memset(f,-1,sizeof(f));
   while(m--)
{scanf("%d%d",&u,&v);pos[++op].v=v; pos[op].next=f[u];f[u]=op;}
 	  for(int i=1;i<=n;i++)//缩图{ if(dfn[i]==0){dfs(i);} }
  for(int i=1;i<=n;i++)//构建新图{int l;
  for(int j=f[i];j!=-1;j=pos[j].next){l=pos[j].v;
 if(in[i]!=in[l]){mm[in[l]]++;}
      }
  }int ss=0;
  for(int i=1;i<=aa;i++) {if(mm[i]==0) ans[++ss]=cc[i];}
  sort(ans+1,ans+ss+1); cout<<ss<<endl;
  for(int i=1;i<ss;i++)
    cout<<ans[i]<<" ";
  if(ss>0)
    cout<<ans[ss]<<endl;
}

    8.双连通

  1. 求割点割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点.

原理:若low[v]>=dfn[u],则u为割点。

int tarjan(int x) {
    v[x]=1;     //点的状态标记,1为已访问,2为割点 
    Dfn[x]=Low[x]=time++;
    for(int i=head[x];i;i=next[i]){
        if(!v[ver[i]]){
            tarjan(ver[i]);
            Low[x]=min(Low[x],Low[ver[i]]);
            if(Dfn[x]<=low[ver[i]]) v[x]++;
        }
        else  Low[x]=min(low[x],Dfn[ver[i]]);
        if((x==1&&v[x]>2)||(x>1&&v[x]>1))  v[x]=2; //对第一个特判 
        else   v[x]=1;
    } 
}

  2.求桥 桥(割边):删掉它之后,图必然会分裂为两个或两个以上的子图。

   原理:若low[v]>dfn[u],则(u,v)为桥。

void tarjan(int x){ v[x]=1;
 Dfn[x]=Low[x]=time++;
 for(int i=head[x];i;i=next[i])
 {
     if(!v[ver[i]])
     {
      p[ver[i]]=edge[i];     //记录父亲边
      tarjan(ver[i]);
      Low[x]=min(Low[x],Low[ver[i]]);
     }
     else if(p[x]!=edge[i])  //不是父亲边则更新
     Low[x]=min(Low[x],Dfn[ver[i]]);
     if(p[x]&&low[x]==dfn[x]) 
     f[p[x]]=1;             //为割边
  }
 }

    3.点双连通分量

   点双连通分支,在求割点的过程中就能把每个点双连通分支求出。建立一个栈,存储双连通分支。在搜索图时,每找到一条树枝边或后向边(非横叉边),就把这条边加入栈中。如果遇到某时满足DFS(u)<=Low(v),说明u是一个割点,同时把边从栈顶一个个取出,直到遇到了边(u,v),取出的这些边与其关联的点,组成一个点双连通分支。

 if(dfn[u]==low[u])
    {
        scc++;
        while(1) //记录每一个点属于的连通块
        {
            v=sta[top--];
            instack[v]=0;
            belong[v]=scc;  //所取出的点即为双连通分支
            if(v==u)
                break;
        }
    }
}

  4.边双连通分支

在求出所有的桥以后,把桥边删除,原图变成了多个连通块,则每个连通块就是一个边双连通分支。桥不属于任何一个边双连通分支,其余的边和每个顶点都属于且只属于一个边双连通分支。

5.一个有桥的连通图,把它通过加边变成边双连通图

首先求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,边连通度为1。

统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。则至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以至少添加的边数就是(leaf+1)/2。

void Tarjan(int u,int fa)
{
    int i,v;
    low[u]=dfn[u]=++cnt;
    sta[++top]=u;
    instack[u]=1;
    for(i=first[u];i!=-1;i=edge[i].next)
    {
        v=edge[i].v;
        if(i==(fa^1))
            continue;
        if(!dfn[v])
        {
            Tarjan(v,i);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        scc++;
        while(1) //记录每一个点属于的连通块
        {
            v=sta[top--];
            instack[v]=0;
            belong[v]=scc;
            if(v==u)
                break;
        }
    }
}for(i=1;i<=n;i++)
        {
            for(j=first[i];j!=-1;j=edge[j].next)
            {
                v=edge[j].v;
                if(belong[i]!=belong[v])
                    degree[belong[i]]++;  //degree为1则为leaf
            }
        }
        int sum=0;
        for(i=1;i<=n;i++)
            if(degree[i]==1)
                sum++;    统计leaf数
}

 

  9.最短路(两种算法分点多还是路多的情况)

void spfa(int st){ int u,v; queue<int>q;
    q.push(st); dis[st]=0;vis[st]=1;
    while(!q.empty()) {
   	  u=q.front(); q.pop(); vis[u]=0;
  	  for(int i=head[u];i!=-1;i=edge[i].next) {v=edge[i].v;
     	  if(dis[u]+edge[i].dis<dis[v]) 
{ dis[v]=dis[u]+edge[i].dis;
          if(vis[v]==0){ vis[v]=1;q.push(v); }
          }
        }
    }
}

void work(){
    int pos = 0,Min;
    memset(vis,0,sizeof(vis));
    for(int i = 0; i <= n; i++)
        low[i] = Map[pos][i];
    vis[pos] = 1;
    for(int i = 0; i < n; i++)
    {Min = MaxInt;
        for(int j = 0; j <= n; j++)
          if(!vis[j] && low[j] < Min){pos = j;Min = low[j];}
        if(Min == MaxInt)break;
        vis[pos] = 1;
        for(int j = 0; j <= n; j++)
            if(!vis[j] && low[j] > low[pos]+Map[pos][j])
                low[j] = low[pos]+Map[pos][j];
    }
    if(low[s] < MaxInt)
        printf("%d\n",low[s]);
    else
        printf("-1\n");
}

10.  2-SAT

  1. 夫妻参加聚会问题
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
const int maxm=maxn*maxn;
struct node{int u,next;}edge[maxm];
int head[maxn],cnt, vis[maxn],dfn[maxn],low[maxn],belong[maxn],id,cnt2;
stack<int>s;
void init(){memset(head,-1,sizeof(head));cnt=0;id=0;
 while(!s.empty())s.pop();
 cnt2=0;memset(vis,0,sizeof(vis));memset(dfn,-1,sizeof(dfn));
memset(low,-1,sizeof(low)); memset(belong,-1,sizeof(belong));}
void addedge(int a,int b){edge[cnt].u=b;edge[cnt].next=head[a];head[a]=cnt++;}
void tarjan(int now)  {dfn[now]=low[now]=id++;vis[now]=1;s.push(now);
for(int i=head[now];i!=-1;i=edge[i].next)   {   int next=edge[i].u;
  if(dfn[next]==-1){ tarjan(next);low[now]=min(low[now],low[next]);}
  else if(vis[next]==1){low[now]=min(low[now],dfn[next]);}
    }
 if(low[now]==dfn[now]){cnt2++;
  while(1){ int tmp;tmp=s.top(),s.pop();vis[tmp]=0;belong[tmp]=cnt2;
      if( tmp==now ) break;}
    }
}
int main(){int n,m;
    while(scanf("%d%d",&n,&m)==2 )  {init();int a,b,c,d;
        while(m--){
 		scanf("%d%d%d%d",&a,&b,&c,&d); 
addedge(2*a+c,2*b+1-d);addedge(2*b+d,2*a+1-c);
        }
        for( int i=0;i<2*n;i++)  if(dfn[i]==-1)  tarjan(i);
        bool flag=true;
        for(int i=0;i<n;i++){
        if(belong[2*i]==belong[2*i+1]){flag=false;break;}
        }
        if(flag) printf("YES\n");   else printf("NO\n");
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值