【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;
    }

转载于:https://www.cnblogs.com/T-Y-P-E/p/10177185.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]中提到了一种树形动态规划的方法来解决CodeForces - 982C问题。在这个问题中,subtree指的是子连通块,而不是子树。为了使cnt_white - cnt_black尽可能大,可以使用两次树形动态规划来求解。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。如果ans\[u\]为正,则减去dp\[v\]就是树链所代表的子树的权值。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] 问题: CodeForces - 982C 树形DP是什么问题?如何解决? 回答: CodeForces - 982C是一个树形动态规划问题。在这个问题中,需要求解子连通块的最大权值和,使得cnt_white - cnt_black尽可能大。解决这个问题的方法是使用两次树形动态规划。第一次是自底向上的过程,维护一个dp数组,表示以每个节点为根的子树中的最大连通块。第二次是自顶向下的过程,处理自底向上过程中无法包含的树链所代表的子树。在第二次遍历中,需要维护一个sum变量,用于存储树链所代表的子树的贡献。根据ans\[u\]的正负,决定是否能对相邻的子节点做出贡献。最终,ans\[u\]代表包含节点u在内的子连通块的最大权值。\[1\] #### 引用[.reference_title] - *1* *2* [CodeForces - 1324F Maximum White Subtree(树形dp)](https://blog.csdn.net/qq_45458915/article/details/104831678)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值