1. 程式人生 > >hdu 拓撲排序歸納

hdu 拓撲排序歸納

拓撲排序,其本質是輸出一個全序關係,對於按要求輸出給定關係的題目,一般就是按照題目要求實現這個全序關係,這種題時常會先給一個偏序關係,然後給出剩下的元素如何建立關係(字典序之類的)。

如果忘了那幾個詞是啥意思...

      偏序關係:滿足自反,反對稱,傳遞性的關係

      全序關係:一個偏序關係R,且對任意x,y有xRy或yRx

      哈斯圖:對一個偏序關係畫的圖,每個點為關係中的元素,其中,對兩點x,y,若有xRy則y畫在x上方,若有xRy且不存在s滿足xRs,sRy則在x,y中連線。一般情況下把題目裡面給的關係直接畫出來再分下層就是哈斯圖了。和正常的哈斯圖不同,為了方便看我們一般會在上面標上箭頭。

    拿下面這個圖說事,按之前的說法,6-2和6-1都在這個偏序關係裡面(傳遞性)但是因為6和1裡面連著2就不在他倆連線了,同樣對於拓撲排序的題,題目裡面說了6-2和2-1以後由傳遞性就有6-1了,不要拘泥於概念

畫上箭頭。拓撲排序的效果在於把偏序關係線性輸出,在這個裡面5,6,7之間沒有確定的順序,還有就是可以按照8-5-6-2這樣的順序,在7之前輸出2,沒有說同層的必須連著輸出。對於這種狀況按什麼順序輸出是一個考點,經常會把這個順序根點的標號扯上關係,加上拓撲排序裡面用到一個佇列,所以時常會在此處把佇列操作成優先佇列


然後就是拓撲排序可以用於判斷有向環,如果在排序中排出來的點比圖上的總點數少就說明有有向環。如果一個關係裡面出現了環,自然就不滿足反對稱性了(ex:xRy,yRz,zRx則由傳遞性有xRz與zRx一起不滿足反對稱性),然後它就不是一個偏序關係了,所以如果判出來了環的情況論上就不會有下一步操作了。無向環用並查集判斷

另外還有的題就是要你確定一個偏序關係,比如統計同樣的偏序(可以理解為哈斯圖上面同一層的元素)每個有多少

因為拓撲排序這個東西本身比較直觀符合大眾認知,直接給個裸的拓撲排序顯得太親民,所以如果看見他出現,一般都會是繞了幾個彎,變成一道智商題,想直接寫一個拓撲排序然後就過了不大現實,搞清楚拓撲排序的思想比較重要,方便在關鍵時刻能夠胡搞的出來。

常用操作:

 反向建圖:有時會出現=需要把題目所給的偏序反過來的情況,比如下面的2647

 把佇列換成優先佇列

比較純的拓撲排序,因為要按照字典序輸出拓撲序相同的元素,以及時間多,所以可以不使用佇列而是暴力遍歷

#include <bits/stdc++.h>

using namespace std;
const int maxn=500;
int main()
{
    int v,e;
    while(cin>>v>>e)
    {
        vector<int>vec;
        vector<int> mp[maxn];
        int deg[maxn],s,t;
        memset(deg,0,sizeof(deg));
        for(int i=1;i<=e;i++)
        {
            cin>>s>>t;
            deg[t]++;
            mp[s].push_back(t);
        }

        for(int i=1;i<=v;i++)
        {
            for(int j=1;j<=v;j++)
            {
                if(deg[j]==0)
                {
                    for(int k=0;k<mp[j].size();k++)
                    {
                        deg[mp[j][k]]--;

                    }
                    deg[j]=INFINITY;
                    vec.push_back(j);
                    break;
                }
            }

        }
        for(int i=0;i<vec.size()-1;i++)
        {
            cout<<vec[i]<<" ";
        }
        cout<<vec[vec.size()-1]<<endl;
    }
}

hdu3342

拓撲排序判斷有向環的題目,對於判環的題,判無向環一般用並查集,判有向環一般用拓撲排序

#include <bits/stdc++.h>

using namespace std;

int main()
{
    vector<int> adjl[105];
    int v,e,s,t,in[105];
    bool vis[105];
    while(cin>>v>>e,v!=0)
    {
        memset(adjl,0,sizeof(adjl));
        memset(vis,0,sizeof(vis));
        memset(in,0,sizeof(in));
        for(int i=0;i<e;i++)
        {
            cin>>s>>t;
            adjl[s].push_back(t);
            in[t]++;
        }
        int coun=v;
        queue<int> q;
        for(int i=0;i<v;i++)
        {
            if(in[i]==0)
            {
                q.push(i);
                vis[i]=true;

            }
        }
     //   for(int i=0;i<v;i++)cout<<in[i]<<" ";
        while(!q.empty())
        {
            int vt=q.front();
           // cout<<vt<<endl;
            q.pop();coun--;
            for(int i=0;i<adjl[vt].size();i++)
            {
                int vv=adjl[vt][i];
                in[vv]--;
                if(!vis[vv]&&in[vv]==0)
                {
                    q.push(vv);
                    vis[vv]=true;
                }
            }
        }
       // cout<<coun<<endl;
        if(coun==0)cout<<"YES"<<endl;
        else cout<<"NO"<<endl;

    }


hdu2647

使用拓撲排序判環並且判斷點在拓撲序上的層次,我的思路是維護兩個值,一個值記錄現在的層次,另一個值維護當前層次下,佇列中還有多少個元素。另外還要反向建圖

#include <bits/stdc++.h>

using namespace std;

int main()
{
    vector<int> adjl[10005];
    int v,e,s,t,in[10005],deg[10005];
    bool vis[10005];
    while(cin>>v>>e)
    {
        memset(adjl,0,sizeof(adjl));
        memset(vis,0,sizeof(vis));
        memset(in,0,sizeof(in));
        for(int i=0;i<e;i++)
        {
            cin>>s>>t;
            adjl[t].push_back(s);
            in[s]++;
        }
        int coun=v;
        queue<int> q;
        int levl=0,cur=0,tot=0;
        for(int i=1;i<=v;i++)
        {
            if(in[i]==0)
            {
                q.push(i);
                vis[i]=true;
                levl++;

            }
        }

     //   for(int i=0;i<v;i++)cout<<in[i]<<" ";
        while(!q.empty())
        {
          // cout<<"!"<<q.size()<<" "<<cur<<" "<<levl<<endl;
            int vt=q.front();
           // cout<<vt<<endl;
           bool flag=false;

            q.pop();levl--;if(levl==0){flag=true;}coun--;tot+=cur+888;//cout<<cur<<endl;
            if(flag)cur++;
            for(int i=0;i<adjl[vt].size();i++)
            {
                int vv=adjl[vt][i];
                in[vv]--;
                if(!vis[vv]&&in[vv]==0)
                {
                    q.push(vv);
                    vis[vv]=true;
                }
            }
            if(flag)levl=q.size();//cout<<q.size()<<endl;
        }
    //   cout<<coun<<endl;
        if(coun==0)cout<<tot<<endl;
        else cout<<"-1"<<endl;

    }

}


hdu1811
這個題是拓撲排序和並查集的結合,需要拿並查集預處理後拿拓撲排序判斷是否有唯一拓撲序和是否有拓撲序
#include <bits/stdc++.h>

using namespace std;
const int maxn=10005;
int fa[maxn];

int findfa(int vt)
{
    if(vt!=fa[vt]) fa[vt]=findfa(fa[vt]);
    return fa[vt];
}
struct edge
{
    int from,to;
    char op;
}ed[maxn*2];

int main()
{

    int v,e,s,t,in[maxn];
    vector<int> adjl[maxn];
    while(~scanf("%d%d",&v,&e))
    {
        for(int i=0;i<v;i++)fa[i]=i;
        memset(ed,0,sizeof(ed));
        memset(adjl,0,sizeof(adjl));
        memset(in,0,sizeof(in));
        char c;
        int sum=v;
        for(int i=0;i<e;i++)
        {
            scanf("%d %c %d",&ed[i].from,&ed[i].op,&ed[i].to);
            int x=findfa(ed[i].from),y=findfa(ed[i].to);
            if(ed[i].op=='='&&x!=y)
            {
                sum--;
                fa[y]=x;
            }

        }
        //cout<<fa[1]<<" "<<fa[2];
        for(int i=0;i<e;i++)
        {
            if(ed[i].op=='=')continue;
            int x=findfa(ed[i].from),y=findfa(ed[i].to);

             if(ed[i].op=='>')
            {
                adjl[x].push_back(y);
                in[y]++;
            }
            else{adjl[y].push_back(x);in[x]++;}
        }
        queue<int>q;
        for(int i=0;i<v;i++)
        {
            if(in[i]==0&&fa[i]==i)
            {
                q.push(i);
            }
        }

       // cout<<endl<<"\\"<<q.empty()<<endl;
        bool flag=false;
        while(!q.empty())
        {
            if(q.size()>1)flag=true;
            int vt=q.front();
            q.pop();
            sum--;//cout<<"  !"<<sum<<"!  ";
            for(int i=0;i<adjl[vt].size();i++)
            {
                in[adjl[vt][i]]--;
                if(in[adjl[vt][i]]==0)q.push(adjl[vt][i]);
            }
        }

        if(sum>0)cout<<"CONFLICT"<<endl;
        else if(flag)cout<<"UNCERTAIN"<<endl;
        else cout<<"OK"<<endl;
       // cout<<sum;

    }



    return 0;
}

這個題把拓撲排序用的佇列換成優先佇列以後再處理一下就行了

#include <bits/stdc++.h>

using namespace std;
vector<int> adjl[100010];
    long long c,su;
    int v,e,s,t,in[100010];int ans[100010],countt;
void top_sort()
{priority_queue<int> q;
 for(int i=v;i>=1;i--)

            if(in[i]==0)

                q.push(i);

     while(!q.empty())
        {

            int vt=q.top();
            q.pop();
            ans[countt++]=vt;


            //c=min((long long)vt,c);

            //su+=c<vt?c:c=vt;


            for(int i=0;i<adjl[vt].size();i++)
            {

                in[adjl[vt][i]]--;
                if(in[adjl[vt][i]]==0)
                {
                    q.push(adjl[vt][i]);

                }
            }

        }
}







int main()
{
    int tt;


    scanf("%d",&tt);
    while(tt--)

    {

         scanf("%d%d",&v,&e);

         su=0;
     /*   memset(adjl,0,sizeof(adjl));
        memset(in,0,sizeof(in));*/
        for(int i=1;i<=v;i++){adjl[i].clear();in[i]=0;}
        for(int i=0;i<e;i++)
        {
            scanf("%d%d",&s,&t);
            adjl[s].push_back(t);
            in[t]++;

        }






countt=0;
       top_sort();
        c=INFINITY;
        for(int i=0;i<countt;i++)
        {
            //su+=c<ans[i]?c:c=ans[i];
            c=c>ans[i]?ans[i]:c;
            su+=c;
        }
    printf("%I64d\n",su);
        //for(int i=0;i<vec.size();i++)cout<<vec[i]<<" ";

      //  cout<<su<<endl;

    }
    return 0;

}

hdu4857

反向建圖,大根堆做佇列,反向輸出

#include <bits/stdc++.h>

using namespace std;
const int maxn=30005;
int main()
{
    int v,e,in[maxn];
    bool vis[maxn];
    int tt;
    vector<int> adjl[maxn];scanf("%d",&tt);
    while(tt--)
    {scanf("%d %d",&v,&e);
        for(int i=1;i<=v;i++)
        {
            adjl[i].clear();
            in[i]=0;
            vis[i]=false;
        }
        int fr,to;
        for(int i=1;i<=e;i++)
        {
            scanf("%d %d",&fr,&to);
            adjl[to].push_back(fr);
            in[fr]++;
        }
        int coun=1;
        //top_sort;
        priority_queue<int>q;
        vector<int>vec;
        for(int i=1;i<=v;i++)if(in[i]==0){q.push(i);}
        while(!q.empty())
        {
            int vt=q.top();
            vec.push_back(vt);
         //   cout<<vt<<" "<<q.size()<<endl;
           // printf("%d",vt);
          //  if(coun++!=v)printf(" ");else printf("\n");
            q.pop();
            for(int i=0;i<adjl[vt].size();i++)
            {
                in[adjl[vt][i]]--;
                if(in[adjl[vt][i]]==0)q.push(adjl[vt][i]);
            }
        }
        for(int i=vec.size()-1;i>0;i--)printf("%d ",vec[i]);
        printf("%d\n",vec[0]);
    }
    return 0;
}