[BZOJ1969][Ahoi2005]LANE 航线规划(树链剖分+并查集)

题目:

我是超链接

题解:

一看这种断边的就知道要【倒悬并查集】,倒序加边
因为时刻保证任意两点相连,那他起码是一棵树,我们倒序加边,加成一棵树之后,每增加一条非树边,就意味着在这条边的两端点的路径上+1,询问的时候只需要统计路径上有多少为0的边

思路还是挺好想,但是在我写的时候有一些小问题:对于这种边权下放为点权的,in[x]+1和in[son[x]]不相等,因为可能没有重儿子,当两个点重合并都为叶子节点的时候就凉了

代码:

#include <cstdio>
#include <iostream>
using namespace std;
const int N=30005;
const int M=100005;
const int A=40005;
struct hh{int x,y,id;}a[M],ask[A];
int n,tot,yx,nxt[N*2],point[N],v[N*2],tot1,nxt1[N*2],point1[N],v1[N*2],c[N*2],f[N],size[N],h[N],son[N],top[N],in[N],num,cnt,answer[A],sum[N*4],delta[N*4];
bool xz[M],vis[M];
int find(int x)
{
    if (f[x]!=x) f[x]=find(f[x]);
    return f[x];
}
void addline(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void addline1(int x,int y,int id){++tot1; nxt1[tot1]=point1[x]; point1[x]=tot1; v1[tot1]=y; c[tot1]=id;}
void dfs_1(int x,int fa)
{
    f[x]=fa; size[x]=1; h[x]=h[fa]+1;int maxx=0;
    for (int i=point[x];i;i=nxt[i])
      if (v[i]!=fa) 
      {
        dfs_1(v[i],x);
        size[x]+=size[v[i]];
        if (maxx<size[v[i]]) maxx=size[v[i]],son[x]=v[i];
      }
}
void dfs_2(int x,int fa)
{
    if (son[fa]!=x) top[x]=x;
    else top[x]=top[fa];
    in[x]=++yx;
    if (son[x])
    {
        dfs_2(son[x],x);
        for (int i=point[x];i;i=nxt[i])
          if (v[i]!=fa && v[i]!=son[x]) dfs_2(v[i],x);
    }   
}
void updata(int now){sum[now]=sum[now<<1]+sum[now<<1|1];}
void build(int now,int l,int r)
{
    delta[now]=-1;
    if (l==r)
    {
        sum[now]=1;
        if (l==1) sum[now]=0;
        return;
    }
    int mid=(l+r)>>1;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    updata(now);
}
void pushdown(int now)
{
    if (delta[now]==0)
    {
        delta[now<<1]=delta[now<<1|1]=0;
        sum[now<<1]=sum[now<<1|1]=0;
        delta[now]=-1;
    }
}
void change(int now,int l,int r,int lrange,int rrange)
{
    if (lrange<=l && rrange>=r){sum[now]=0; delta[now]=0; return;}
    int mid=(l+r)>>1;pushdown(now);
    if (lrange<=mid) change(now<<1,l,mid,lrange,rrange);
    if (rrange>mid) change(now<<1|1,mid+1,r,lrange,rrange);
    updata(now);
}
int qurry(int now,int l,int r,int lrange,int rrange)
{
    if (lrange<=l && rrange>=r) return sum[now];
    int mid=(l+r)>>1,ans=0;pushdown(now);
    if (lrange<=mid) ans+=qurry(now<<1,l,mid,lrange,rrange);
    if (rrange>mid) ans+=qurry(now<<1|1,mid+1,r,lrange,rrange);
    return ans;
}
void Chan(int u,int v,int id)
{
    int f1=top[u],f2=top[v],ans=0;
    while (f1!=f2)
    {
        if (h[f1]<h[f2]) swap(f1,f2),swap(u,v);
        if (id==1) change(1,1,n,in[f1],in[u]);
        else ans+=qurry(1,1,n,in[f1],in[u]);
        u=f[f1]; f1=top[u];
    }
    if (in[u]>in[v]) swap(u,v);
    if (id==1) change(1,1,n,in[u]+1,in[v]);//////
    else 
    {
        ans+=qurry(1,1,n,in[u]+1,in[v]);
        answer[++cnt]=ans;
    }
}
int main()
{
    int m;scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d",&a[i].x,&a[i].y);
        if (a[i].x>a[i].y) swap(a[i].x,a[i].y);
        addline1(a[i].x,a[i].y,i);
    }
    int id,x,y;
    while (1)
    {
        scanf("%d",&id);if (id==-1) break;
        if (id==1)
        {
            scanf("%d%d",&x,&y);
            ask[++num].x=x; ask[num].y=y; ask[num].id=1;
        }
        else
        {
            scanf("%d%d",&x,&y);
            if (x>y) swap(x,y);
            ask[++num].x=x; ask[num].y=y; ask[num].id=0;
            for (int i=point1[x];i;i=nxt1[i])
              if (v1[i]==y) {xz[c[i]]=1;break;}
        }
    }
    int bs=0;
    for (int i=1;i<=n;i++) f[i]=i;
    for (int i=1;i<=m;i++)
      if (!xz[i])
      {
        int x=find(a[i].x),y=find(a[i].y);
        if (x!=y) 
        {
            f[x]=y;vis[i]=1;
            addline(a[i].x,a[i].y);
            bs++;if (bs==n-1) break;
        }
      }
    dfs_1(1,0); 
    dfs_2(1,0);
    build(1,1,n);
    for (int i=1;i<=m;i++) 
      if (!xz[i] && !vis[i]) Chan(a[i].x,a[i].y,1);
    for (int i=num;i>=1;i--)
      if (ask[i].id==1) Chan(ask[i].x,ask[i].y,0);
      else Chan(ask[i].x,ask[i].y,1);
    for (int i=cnt;i>=1;i--) printf("%d\n",answer[i]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值