zoukankan      html  css  js  c++  java
  • 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;
    }
     
  • 相关阅读:
    CAD迷你看图
    CAD打开文件总是弹出要求选择字体怎么办
    CAD字体显示问号的解决办法
    如何让IE8的菜单栏调到最上方
    苹果公司在 1980 年上市时为什么不使用双层股权架构来确保乔布斯有着足够的控制权?
    怪不的软件开发这么挣钱,原来是有这么多职位
    剖析余额宝“好”与“坏”
    网上盗刷事件频发 风险肇始于“快捷支付”?
    招商银行网银专业版怎么消除安全隐患
    《浅谈磁盘控制器驱动》,磁盘控制器驱动答疑解惑![2012.1.29完结]by skyfree
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9031509.html
Copyright © 2011-2022 走看看