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

 

 
posted @ 2018-05-13 11:13  *Miracle*  阅读(183)  评论(0编辑  收藏  举报