BZOJ1969 [Ahoi2005]LANE 航线规划(洛谷P2542)

292 篇文章 1 订阅
281 篇文章 1 订阅

树链剖分 (Tarjan)

BZOJ题目传送门
洛谷题目传送门

首先和水管局长(自己的链接)一样,先把边删完,建出最终的图。然后把操作倒过来,转删边为添边。

然后有两种方法把这张图变成树:
1.建出这个图的一颗生成树,把其他多余的边当作添边处理。

2.Tarjan缩点后处理。

对这个树进行树链剖分维护边权(刚开始都是1)。添边就把这两个点的路径上的边权都变成0,询问就直接查询路径点权和即可。

代码采用的是第二种做法,因为我第一种方法没调出来直接。但第一种做法代码量会小很多。

代码(太长了我分分段):

#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 30005
#define K 40005
#define M 100005
#define F inline
using namespace std;
struct tree{ int l,r,f,sum; }t[N<<2];
struct edge{ int next,to; }ed[M<<2];
struct edg{ int x,y,id; }e[M];
struct query{ int x,y,f; }q[K];
int n,m,k,nd,p,ti,fa[N][20],dep[N],tp[N],to[N],sz[N],id[N],in[N];
int top,kk,nq,np,stk[N],dfn[N],low[N],num[N],h1[N],h[N],ans[K];
bool f[N];
//IO优化
F char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
F int _read(){
    int x=0,f=1; char ch=readc();
    while (!isdigit(ch)) { if (ch=='-') f=-1; ch=readc(); }
    while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
    return x*f;
}
F void writec(int x){ if (x>9) writec(x/10); putchar(x%10+48); }
F void _write(int x){ writec(x),putchar('\n'); }

F bool cmp(edg a,edg b){ return a.x==b.x?a.y<b.y:a.x<b.x; }
F void addedge(int *h,int x,int y){ ed[++k]=(edge){h[x],y},h[x]=k; }
F int ef(int x,int y){
    int l=1,r=m,mid;
    while (l<=r){
        int p=e[mid=l+r>>1].x,q=e[mid].y;
        if (p<x||p==x&&q<y) l=mid+1;
        else if (p>x||p==x&&q>y) r=mid-1;
        else return mid;
    }
}
//树剖的两遍DFS
void dfs1(int x,int dth){
    sz[x]=1,dep[x]=dth;
    for (int i=h[x],v;i;i=ed[i].next)
        if ((v=ed[i].to)!=fa[x][0]){
            fa[v][0]=x,dfs1(v,dth+1),sz[x]+=sz[v];
            if (sz[to[x]]<sz[v]) to[x]=v;
        }
}
void dfs2(int x){
    if (to[in[id[x]=++nd]=x]) tp[to[x]]=tp[x],dfs2(to[x]);
    for (int i=h[x],v;i;i=ed[i].next)
        if ((v=ed[i].to)!=fa[x][0]&&v!=to[x])
            tp[v]=v,dfs2(v);
}
//线段树
F void pshd(int x){
    t[x<<1].f=t[x<<1|1].f=t[x].f,t[x].f=-1;
    t[x<<1].sum=(t[x<<1].r-t[x<<1].l+1)*t[x<<1].f;
    t[x<<1|1].sum=(t[x<<1|1].r-t[x<<1|1].l+1)*t[x<<1|1].f;
}
void build(int x,int l,int r){
    t[x].l=l,t[x].r=r,t[x].sum=r-l+1,t[x].f=-1;
    if (l==r) return; int mid=l+r>>1;
    build(x<<1,l,mid),build(x<<1|1,mid+1,r);
}
void mdfy(int x,int l,int r,int w){
    if (t[x].r<l||t[x].l>r) return;
    if (t[x].l>=l&&t[x].r<=r){
        t[x].f=w,t[x].sum=(t[x].r-t[x].l+1)*w; return;
    }
    if (~t[x].f) pshd(x);
    mdfy(x<<1,l,r,w),mdfy(x<<1|1,l,r,w);
    t[x].sum=t[x<<1].sum+t[x<<1|1].sum;
}
int srch(int x,int l,int r){
    if (t[x].r<l||t[x].l>r) return 0;
    if (t[x].l>=l&&t[x].r<=r) return t[x].sum;
    if (~t[x].f) pshd(x);
    return srch(x<<1,l,r)+srch(x<<1|1,l,r);
}
//树剖
F void nsrt(int x,int y){
    while (tp[x]!=tp[y]){
        if (dep[tp[x]]<dep[tp[y]]) swap(x,y);
        mdfy(1,id[tp[x]],id[x],0),x=fa[tp[x]][0];
    }
    if (dep[x]<dep[y]) swap(x,y);
    mdfy(1,id[y],id[x],0);
}
F int find(int x,int y){
    int ans=0;
    while (tp[x]!=tp[y]){
        if (dep[tp[x]]<dep[tp[y]]) swap(x,y);
        ans+=srch(1,id[tp[x]],id[x]),x=fa[tp[x]][0];
    }
    if (dep[x]<dep[y]) swap(x,y);
    return ans+srch(1,id[y],id[x]);
}
//缩点
void Tarjan(int x,int e){
    low[x]=dfn[x]=++p,stk[++top]=x,f[x]=true;
    for (int i=h1[x],v;i;i=ed[i].next)
        if (i!=e)
            if (!dfn[v=ed[i].to])
                Tarjan(v,i^1),low[x]=min(low[x],low[v]);
            else low[x]=min(low[x],dfn[v]);
    if (low[x]==dfn[x]){
        ti++;
        while (stk[top+1]!=x)
            num[stk[top]]=ti,f[stk[top--]]=false;
    }
}
//LCA
F void Make(){
    for (int j=1;j<=16;j++)
        for (int i=1;i<=n;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
}
F int LCA(int x,int y){
    if (dep[x]<dep[y]) swap(x,y);
    for (int j=16;~j;j--)
        if (dep[fa[x][j]]>=dep[y]) x=fa[x][j];
    if (x==y) return x;
    for (int j=16;~j;j--)
        if (fa[x][j]!=fa[y][j])
            x=fa[x][j],y=fa[y][j];
    return fa[x][0];
}
int main(){
    n=_read(),m=_read(),k=1;
    for (int i=1,x,y;i<=m;i++){
        x=_read(),y=_read(); if (x>y) swap(x,y);
        e[++kk]=(edg){x,y,0};
    }
    sort(e+1,e+kk+1,cmp);
    for (int fl=_read(),x,y;~fl;fl=_read()){
        x=_read(),y=_read(); if (x>y) swap(x,y);
        q[++nq].f=fl,q[nq].x=x,q[nq].y=y;
        if (!fl) e[ef(x,y)].id=1,np++;
    }
    for (int i=1;i<=kk;i++)
        if (!e[i].id){
            addedge(h1,e[i].x,e[i].y);
            addedge(h1,e[i].y,e[i].x);
        }
    for (int i=1;i<=n;i++) if (!dfn[i]) Tarjan(i,0);
    for (int i=1;i<=n;i++)
        for (int j=h1[i],v;j;j=ed[j].next)
            if (num[i]!=num[ed[j].to])
                addedge(h,num[i],num[ed[j].to]);
    dfs1(1,1),tp[1]=1,dfs2(1),build(1,1,n),Make();
    for (int i=nq;i;i--){
        int x=num[q[i].x],y=num[q[i].y],lca=LCA(x,y),lst;
        if (!q[i].f){
            lst=srch(1,id[lca],id[lca]),nsrt(x,y);
            if (lst) mdfy(1,id[lca],id[lca],1);
        }
        else ans[++ans[0]]=find(x,y)-find(lca,lca);
    }
    for (int i=ans[0];i;i--) _write(ans[i]); return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值