1. 程式人生 > >【Codeforces】【網路流】【樹鏈剖分】【線段樹】ALT (CodeForces - 786E)

【Codeforces】【網路流】【樹鏈剖分】【線段樹】ALT (CodeForces - 786E)

題意

現在有m個人,每一個人都特別喜歡狗。另外還有一棵n個節點的樹。
現在每個人都想要從樹上的某個節點走到另外一個節點,且滿足要麼這個人自帶一條狗m,要麼他經過的所有邊h上都有一條狗。
2<=n<=2*10^4,1<=m<=10^4

輸入格式

第一行為兩個整數n,m,分別表示樹的大小和人數。
接下來有n-1行,每一行有兩個整數u,v,表示書上有一條u-v的邊。
再接下來有m行,每一行兩個整數x[i],y[i]表示第i個人想從x[i]走到y[i]。

輸出格式

第一行為一個整數k,表示一共有多少條狗。
第二行開頭為一個整數表示一個有多少個人自帶狗,然後是帶狗的人的編號。
第三行開頭為一個整數e,表示有多少條邊上放了狗,然後是e個整數表示放了狗的邊的編號。

思路

首先,和Oleg and chess (CodeForces - 793G)類似的

源點連向每一個人,然後每一個人連向每一條這個人的路徑經過的邊,然後所有的邊再連向匯點。然後跑最大流得到最小割,割掉的邊如果是與人相連的就是這個人自帶狗;如果是與邊相連的,就是這條邊上放了狗,就可以求方案了。
然而現在問題來了,我們可以很輕鬆的構造一些資料使得每一個人連出去的邊高達O(n)條,是的總的邊數達到O(n*n)條,然後乖乖T掉,這就不太好了。

下面我們考慮優化。

根據網上的題解,k大概有兩種優化建圖的方式,也就是倍增優化建圖以及樹鏈剖分優化,這裡主要介紹後者。
話說樹剖還真是個好東西,直接就讓數軸上的演算法跑到了樹上,常數還小,甚至媲美O(nlogn)


回到正題,還是看這道題如何用樹剖優化。對於樹上的x[i]到y[i],我們可以在樹剖往上爬的時候,將這條路徑對應到線段樹上的若干個子線段,然後在讓第i個人與其一一連邊。個人感覺就是開頭那道題的簡化版拿到樹上之後的操作。這樣子連邊就是十分優秀的了,至少不是nn的了m,但估計是nlog^2(n)級別的複雜度。就這樣,我們就成功的將建圖優化了。

但是這都不夠!!因為它還要輸出方案。在前文所說的暴力的方法中,我們已經有了一個大概的思路,然而還是有具體的細節需要說明:
首先是如何找割邊。因為流量的特殊性,所以說割邊一定是與源點g或者是匯點直接相連的。我們可以在跑完網路流之後,再從源點開始遍歷整個圖,只走有殘餘容量的邊,然後能夠到達的點就一定是屬於S這半邊的,然後其餘的點就是屬於T那半邊的了。
分成兩部分之後,割邊自然就是u->v,其中u屬於S那邊,v屬於T那邊。然後就找到割邊了。
這裡值得一提的是,為了加快演算法,最後可以只看與源點或匯點直接相連的邊是不是割邊(自行理解)。
這裡還是給出圖的大概樣子。
在這裡插入圖片描述


其中右邊那個就是線段樹了。

程式碼

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #define MAXN 20000
    #define INF 0x3FFFFFFF
    using namespace std;
    struct edge
    {
        int to,id;
        edge(){};
        edge(int _to,int _id):to(_to),id(_id){};
    };
    struct node
    {
        int ch[2];
    }tree[MAXN*4];
    struct Node
    {
        int to,cap;
        Node *nxt,*bck;
    }edges[MAXN*500];
    Node *ncnt=&edges[0],*Adj[MAXN*10+5];
    int tcnt;
    vector<edge> G[MAXN+5];
    //vector<int> seq;
    int S,T;
    int n,m,dcnt,rt;
    int fro[MAXN+5],to[MAXN+5];
    int edid[MAXN*500+5];
    int treefa[MAXN+5],faid[MAXN+5],dep[MAXN+5],dfn[MAXN+5],rnk[MAXN+5];
    int son[MAXN+5],top[MAXN+5],siz[MAXN+5];
    int d[MAXN*10+5],vd[MAXN*10+5];
    bool vis[MAXN*10+5],spedge[MAXN*500+5],spper[MAXN+5];
    void AddEdge(int u,int v,int cap)
    {
        Node *p=++ncnt;
        p->to=v;p->cap=cap;
        p->nxt=Adj[u];Adj[u]=p;
        
        Node *q=++ncnt;
        q->to=u;q->cap=0;
        q->nxt=Adj[v];Adj[v]=q;
        
        p->bck=q,q->bck=p;
    }
    void DFS1(int u,int fa)
    {
        siz[u]=1;
        for(int i=0;i<(int)G[u].size();i++)
        {
            int v=G[u][i].to,id=G[u][i].id;
            if(v==fa)
                continue;
            dep[v]=dep[u]+1;
            faid[v]=id;treefa[v]=u;
            DFS1(v,u);
            siz[u]+=siz[v];
            if(son[u]==0||siz[v]>siz[son[u]])
                son[u]=v;
        }
    }
    void DFS2(int u,int fa,int tp)
    {
        dfn[u]=++dcnt;rnk[dcnt]=u;
        top[u]=tp;
        if(son[u]!=0)
            DFS2(son[u],u,tp);
        for(int i=0;i<(int)G[u].size();i++)
        {
            int v=G[u][i].to;
            if(v==fa||v==son[u])
                continue;
            DFS2(v,u,v);
        }
    }
    void Build_SegTree(int &p,int l,int r)
    {
        p=++tcnt;
        if(l==r)
            return;
        int mid=(l+r)/2;
        Build_SegTree(tree[p].ch[0],l,mid);
        Build_SegTree(tree[p].ch[1],mid+1,r);
    }
    void Bianli_SegTree(int p,int l,int r)
    {
        if(l==r)
        {
            edid[p]=faid[rnk[l]];
            AddEdge(p,T,1);
            return;
        }
        int mid=(l+r)/2;
        AddEdge(p,tree[p].ch[0],INF);
        AddEdge(p,tree[p].ch[1],INF);
        Bianli_SegTree(tree[p].ch[0],l,mid);
        Bianli_SegTree(tree[p].ch[1],mid+1,r);
    }
    void Process_Tree()
    {
        DFS1(1,-1);
        DFS2(1,-1,1);
        tcnt=m;
        Build_SegTree(rt,1,n);
        S=0,T=tcnt+1;
        Bianli_SegTree(rt,1,n);
    }
    void Query_SegTree(int p,int l,int r,int ql,int qr,int per)
    {
        if(qr<l||ql>r)
            return;
        if(ql<=l&&r<=qr)
        {
            AddEdge(per,p,INF);
            return;
        }
        int mid=(l+r)/2;
        Query_SegTree(tree[p].ch[0],l,mid,ql,qr,per);
        Query_SegTree(tree[p].ch[1],mid+1,r,ql,qr,per);
    }
    void Query_Tree(int per,int x,int y)
    {
        while(top[x]!=top[y])
        {
            if(dep[top[x]]<dep[top[y]])
                swap(x,y);
            Query_SegTree(rt,1,n,dfn[top[x]],dfn[x],per);
            x=treefa[top[x]];
        }
        if(dep[x]>dep[y])
            swap(x,y);
        if(x!=y)
            Query_SegTree(rt,1,n,dfn[son[x]],dfn[y],per);
    }
    void Build_Gragh()
    {
        for(int i=1;i<=m;i++)
        {
            AddEdge(S,i,1);
            Query_Tree(i,fro[i],to[i]);//利用樹鏈剖分的詢問操作建邊
        }
    }
    int aug(int u,int tot)
    {
        if(u==T)
            return tot;
        int sum=0,mind=T+1,delta,v;
        for(Node *p=Adj[u];p!=NULL;p=p->nxt)
        {
            v=p->to;
            if(p->cap>0)
            {
                if(d[u]==d[v]+1)
                {
                    delta=min(tot-sum,p->cap);
                    delta=aug(v,delta);
                    sum+=delta;
                    p->cap-=delta,p->bck->cap+=delta;
                    if(d[S]>=T+1)
                        return sum;
                    if(sum==tot)
                        break;
                }
                mind=min(mind,d[v]);
            }
        }
        if(sum==0)
        {
            vd[d[u]]--;
            if(vd[d[u]]==0)
                d[S]=T+1;
            d[u]=mind+1;
            vd[d[u]]++;
        }
        return sum;
    }
    int Isap()
    {
        int flow=0;
        memset(d,0,sizeof(d));
        memset(vd,0,sizeof(vd));
        vd[0]=T+1;
        while(d[S]<T+1)
            flow+=aug(S,INF);
        return flow;
    }
    void DFS(int u)
    {
        vis[u]=true;
        for(Node *p=Adj[u];p!=NULL;p=p->nxt)
        {
            int v=p->to;
            if(p->cap>0&&vis[v]==false)
                DFS(v);
        }
    }
    void Get_Plan()
    {
        DFS(S);//DFS求出與S相連的點是哪些
        for(Node *p=Adj[S];p!=NULL;p=p->nxt)//直接列舉與源點相連的點
        {
            int j=p->to;
            if(vis[j]==false)
                spper[j]=true;//special person
        }
        for(Node *p=Adj[T];p!=NULL;p=p->nxt)//直接列舉與匯點相連的點
        {
            int j=p->to;
            if(vis[j]==true)
                spedge[edid[j]]=true;//special edge
        }
        int sppernum=0,spedgenum=0;
        for(int i=1;i<=m;i++)
            if(spper[i])
                sppernum++;
        for(int i=1;i<=n;i++)
            if(spedge[faid[i]])
                spedgenum++;
        printf("%d",sppernum);
        for(int i=1;i<=m;i++)
            if(spper[i])
                printf(" %d",i);
        printf("\n");
        printf("%d",spedgenum);
        for(int i=1;i<n;i++)
            if(spedge[i]==true)
                printf(" %d",i);
        printf("\n");
    }
    int main()
    {
        scanf("%d %d",&n,&m);
        int u,v;
        for(int i=1;i<n;i++)
        {
            scanf("%d %d",&u,&v);
            G[u].push_back(edge(v,i));
            G[v].push_back(edge(u,i));
        }
        for(int i=1;i<=m;i++)
            scanf("%d %d",&fro[i],&to[i]);
        Process_Tree();//樹鏈剖分預處理
        Build_Gragh();//網路流建圖
    //  if(n==20000)
    //      return 0;
        int ans=Isap();//網路流求答案
        printf("%d\n",ans);
        Get_Plan();//求割掉的邊
        return 0;
    }