• POJ 3417 Network


    本文转载:http://www.cnblogs.com/YoungNeal/p/8530398.html

    题目大意:

    给定一棵树,有 n-1 条树边,m 条非树边,有两次割边的机会,第一次只能割树边,第二次只能割非树边,问有多少种方案使得两次之后树分为两个部分?

    题解: 我们称每条非树边 (x,y) 都把树上 x,y 之间的路径上的每条边“覆盖了一次”。我们只需统计出每条树边被覆盖了次数。若第一步把覆盖 0 次的树边切断,则第二步可以任意切断一条非树边。若第一步把被覆盖 1 次的树边切断,则第二次方法唯一。若第一步把被覆盖 2 次及以上的树边切断,那么第二步无解。

    所以我们接下来要解决的问题模型是:给定一张无向图和一颗生成树,求每条“树边”被“非树边”覆盖了多少次。

    解决此问题有一个经典做法,我们称之为“树上差分算法”。我们给每个节点一个初始为 0 的权值,然后对每条非树边 (x,y) ,令节点 x 的权值加 1,节点 y 的权值加 1,节点 LCA(x,y) 的权值减 2。最后对这棵生成树进行一次深度优先遍历,求出 F[x] 表示以 x 为根的子树中各节点权值的和。F[x] 就是 x 与它的父节点之间的“树边”被覆盖的次数。时间复杂度 O(N+M)。

                         --《算法竞赛进阶指南》 注意最后求 ans 时,要从 2 开始,因为 1 的权值一定为 0。

    (求树上路径被覆盖次数都可以采用树上差分)

    代码:

    #include<cstdio>
    #include<cstring>
    #define N 100005
    #define int long long
    using namespace std;
    
    bool vis[N];
    int head[N];
    int cf[N],q[N];
    int n,m,cnt,ans;
    int d[N],f[N][30];
    
    struct Edge{
        int to,nxt;
    }edge[N<<1];
    
    void add(int x,int y){
        edge[++cnt].to=y;
        edge[cnt].nxt=head[x];
        head[x]=cnt;
    }
    
    void dfs(int now){
        for(int i=head[now];i;i=edge[i].nxt){
            int to=edge[i].to;
            if(d[to]) continue;
            d[to]=d[now]+1;
            f[to][0]=now;
            for(int k=1;k<=21;k++)
                f[to][k]=f[f[to][k-1]][k-1];
            dfs(to);
        }
    }
    
    int lca(int x,int y){
        if(d[x]<d[y]) x^=y^=x^=y;
        for(int k=21;~k;k--){
            if(d[f[x][k]]>=d[y]) x=f[x][k];
        }
        if(x==y) return y;
        for(int k=21;~k;k--){
            if(f[x][k]!=f[y][k])
                x=f[x][k],y=f[y][k];
        }
        return f[x][0];
    }
    
    void dfs2(int now){
        vis[now]=1;
        for(int i=head[now];i;i=edge[i].nxt){
            int to=edge[i].to;
            if(!vis[to]){
                dfs2(to);
                cf[now]+=cf[to];
            }
        }
    }
    
    signed main(){
        scanf("%lld%lld",&n,&m);
        for(int x,y,i=1;i<n;i++){
            scanf("%lld%lld",&x,&y);
            add(x,y);add(y,x);
        }
        d[1]=1;
        dfs(1);
        for(int x,y,i=1;i<=m;i++){
            scanf("%lld%lld",&x,&y);
            cf[x]++,cf[y]++;
            cf[lca(x,y)]-=2;
        }
        dfs2(1);
        for(int i=2;i<=n;i++){
            if(cf[i]==0) ans+=m;
            if(cf[i]==1) ans++;
        }
        printf("%lld",ans);
        return 0;
    }
     
  • 相关阅读:
    leetcode(5)-罗马数字转整数
    leetcode(4)-整数反转
    leetcode(3)-回文数
    leetcode(2)-有效的括号
    leetcode(1)-两数之和
    HTTP基础(一)
    ubuntu 18.04安装MariaDB 10.04并通过远程navicat连接
    ubuntu18.04 root用户登录
    xshell连接ubuntu虚拟机
    ubuntu18.04使用node压缩包的安装及配置
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9031509.html
Copyright © 2020-2023  润新知