POJ3417 Network (闇の連鎖)

### 树上差分 lca

POJ3417
闇の連鎖
奇奇妙妙的树上差分
主要边是树的结构
附加边让树的结构出现环
手画可以发现
\(xy\)为附加边,假如第一次切断了\(xy\)通过树边相连的路径
也就是破坏了环
那么第二次就必定要切断\(xy\)
那么就可以通过边差分来解决这个问题
假如\(x\)\(y\)之间有附加边
在这两个点上记\(+1\),表示它到它的父亲的边被覆盖
在lca处\(-2\)来差分
dfs求子树和就是当前点到父亲边的覆盖次数
1.假如覆盖次数为\(2\)证明没有办法两刀
2.假如覆盖次数为\(1\)只能切它子树的附加边
3.假如覆盖次数为\(0\)就可以切m个附加边的任意一条
注意两个OJ数据范围不一样(POJ vector会T,用G++差不多水过去)


代码如下:

//#pragma GCC optimize(2)
#include<cstdio>
#include<vector>
#include<map>
#include<cstring>
using namespace std;
const int maxn=100005;
struct node{
    int nxt,to;
    #define nxt(x) e[x].nxt
    #define to(x) e[x].to
}e[maxn<<1];
int id[maxn],n,m,res,head[maxn],tot,fa[maxn],lc[maxn],d[maxn],f[maxn];
inline void add(int x,int y){
    to(++tot)=y;nxt(tot)=head[x];head[x]=tot;
}
vector<int> as[maxn],as_id[maxn];
pair<int,int> q[maxn];
int find(int x){return fa[x]==x ? fa[x] : fa[x]=find(fa[x]);}
void tarjan(int x){
    id[x]=1;
    for(int i=head[x];i;i=nxt(i)){
        if(id[to(i)]) continue;
        tarjan(to(i));
        fa[to(i)]=x;
    }
    for(unsigned int i=0;i<as[x].size();i++){
        if(id[as[x][i]]==2)
            lc[as_id[x][i]]=find(as[x][i]);
    }
    id[x]=2;
}
void dfs(int x){
    id[x]=1;
    for(int i=head[x];i;i=nxt(i)){
        if(id[to(i)]) continue;
        dfs(to(i));
        f[x]+=f[to(i)];
    }
    f[x]+=d[x];
    if(!f[x]) res+=m;
    else if(f[x]==1) res+=1;
}
int main()
{
    //freopen("1.in","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<n;i++){
        int x,y;scanf("%d%d",&x,&y);add(x,y);add(y,x);
        fa[i]=i;
    }
    fa[n]=n;
    for(int i=1;i<=m;i++){
        int x,y;scanf("%d%d",&x,&y);
        if(x==y) lc[i]=x;
        as[x].push_back(y);as_id[x].push_back(i);
        as[y].push_back(x);as_id[y].push_back(i);
        q[i].first=x;q[i].second=y;
    }
    tarjan(1);
    for(int i=1;i<=m;i++){
        d[q[i].first]++;d[q[i].second]++;
        d[lc[i]]-=2;
    }
    memset(id,0,sizeof(id));
    dfs(1);
    printf("%d",res-m);//去掉1节点
    return 0;
}
01-19 01:43