[bzoj2861] 双向边定向为单向边 解题报告

11 篇文章 0 订阅
3 篇文章 0 订阅

这题搞了好久。。

首先一条双向边(u,v)可以变成单向边的条件是存在一个经过这条边的环,只需要按这个环的方向把这个环上的所有双向边变成单向就可以了,如果这个环上都是双向边,就随便定一个方向就可以了。

所以我们考虑将双向边拆成两条单向边dfs,这样树边至少存在一个向下的方向,但也有可能是向下的单向边。
但是考虑非树边的时候首先注意到一件事情,就是两条非树边之间可能互相影响,就是说一条非树边可能不能与树边构成环,但是可能与另几条非树边构成环。但是,其实这种影响必然是按dfs序从先到后的。
对于一条非树边(u,v),如果(lca,v)的树边中不存在单向边,那么我们就认为这条非树边与从u到v的树边构成了环,并且把(u,lca)的单向边清为双向边。(其实就是把环缩点。。)
这样的话如果(u,v)并不能构成环,那么它在dfs的过程中之后也不会构成环了,因为(lca,v)已经被dfs过了,只有在lca在v一侧的子树中的出边才能消除其中的单向边,而它们已经被dfs过了。(所以说是没有后效性的!)

所以我们可以在dfs过程中顺便tarjan lca,然后再用一个并查集维护一条边上面第一条单向边即可。

#include<cstdio>
#include<iostream>
using namespace std;
#include<algorithm>
#include<cstring>
const int N=1e5+5,M=2e5+5;
char * cp=(char *)malloc(4000000);
inline void in(int &x)
{
    while(*cp<'0'||*cp>'9')++cp;
    for(x=0;*cp>='0'&&*cp<='9';)x=x*10+(*cp++^'0');
}

int next[M*2],succ[M*2],ptr[N],etot=1;
int other[M*2];
inline void addedge(int u,int v)
{
    next[etot]=ptr[u],ptr[u]=etot,succ[etot++]=v;
}

int ans;
int delta[N];
int depth[N];
bool vst[N];
int ftr[N];
int fa[N],ff[N];
int find(int fa[],int x)
{
    return x==fa[x]?x:fa[x]=find(fa,fa[x]);
}
void dfs(int node,int fe)
{
    //printf("dfs(%d):ftr=%d,fe=%d\n",node,ftr[node],(bool)other[fe]);

    depth[node]=depth[ftr[node]]+1;
    fa[node]=node;
    vst[node]=1;
    for(int i=ptr[node];i;i=next[i])
        if(i!=other[fe])
            if(vst[succ[i]])
            {
                //printf("%d->%d:%d\n",node,succ[i],(bool)other[i]);
                if(find(fa,succ[i]))
                {
                    if(depth[fa[succ[i]]]>=depth[find(ff,succ[i])])
                    {
                        if(i<other[i])++ans;

                        while(depth[find(ff,node)]>depth[fa[succ[i]]])ff[ff[node]]=ftr[ff[node]];
                        ++delta[node],++delta[succ[i]],delta[fa[succ[i]]]-=2;

                        //puts("Get");
                    }
                }
            }
            else
            {
                ftr[succ[i]]=node;
                if(other[i])ff[succ[i]]=node;
                else ff[succ[i]]=succ[i];
                dfs(succ[i],i);
            }
    fa[node]=ftr[node];
}
void query(int node,int fe)
{
    vst[node]=1;
    for(int i=ptr[node];i;i=next[i])
        if(!vst[succ[i]])
        {
            query(succ[i],i);
            delta[node]+=delta[succ[i]];
        }
    if(other[fe]&&delta[node])++ans;
}
int main()
{
    freopen("bzoj2861.in","r",stdin);
    freopen("bzoj2861.out","w",stdout);
    fread(cp,1,4000000,stdin);
    int n,m;
    in(n),in(m);
    int u,v,type;
    for(int i=m;i--;)
    {
        in(u),in(v),in(type);
        addedge(u,v);
        if(type==2)
        {
            other[etot]=etot-1,other[etot-1]=etot;
            addedge(v,u);
        }
    }
    for(int i=n;i;--i)
        if(!vst[i])
            dfs(i,0);
    memset(vst,0,sizeof(vst));
    for(int i=n;i;--i)
        if(!vst[i])
            query(i,0);
    printf("%d\n",ans);
}

总结:
①dfs非树边一个非常重要的性质是它必然是从当前点指向已访问过的点。要么是返祖边,要么是指向自己子树,要么是指向另一个dfs序考前的子树。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值