P5163 WD与地图 题解

整体二分+tarjan+权值线段树合并

Statement

P5163 WD与地图 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

给定一张 \(n\) 个点 \(m\) 条边的带点权的有向图,以及 \(q\) 个操作,操作有:

  • 删除一条边(保证存在)
  • 单点修改点权
  • 询问某个点所在的强连通分量内前 \(k\) 大的点权和。

\(n\le 10^5\quad m,q\le 2\times 10^5\)

Solution

看到删边,套路转化成加边

考虑如果题目是无向图,条件联通怎么做

显然,我们可以每次加入一条边的时候合并两个联通块,然后为了快速得到联通块前 \(k\) 大和,我们每一个联通块维护一个权值线段树即可,合并即是线段树合并

考虑有向图强连通,不同之处在于我们不知道一条边加入过后是否会使得两个强连通分量合并

考虑求出每条边能使得加入后两端合并的时间

暴力的想法是对于每一条边,暴力枚举加入时间,每一个时间都 tarjan 判断一下

一个简单的优化是说,因为我们暴枚的时间是递增的,所以没有必要每次都把所有边加一遍

当然,这引出一个问题是说能不能直接在之前图的基础上加边后直接跑 tarjan ,一会儿解释

一个简单的优化是说,我们可以二分时间

对每一条边都做一遍显然比较离谱,我们考虑整体二分

因此,结合上面两个优化,我们得到下面的算法:

每次把时间区间 \([0,mid]\) 中的边加入图中,跑 tarjan 判断这些边是否能够让两个块合并

如果能合并,那么把其放入左区间递归,否则放入右区间

递归时,先递归右边,这样得以利用已经加入的边

再递归左边时,由于右边加了一些需要去掉的边,所以考虑可撤销并查集维护

这样,每一层需要加的边数和做 tarjan 的边数的和都是 \(O(m)\) 的,时间复杂度 \(O((n+m)\log m\log n)\)

最后解释上面那个问题,每次只加入那些边,且只对新加的边做 tarjan 不会存在问题的原因是:

若某条边没被归到当前区间中,要么其在左边已经被合并了,此时并查集中已经缩好点了;要么其在右边才能合并,在当前区间中将其加入没有意义。也不会出现某条边用当前区间中新加的边不能缩点,用 \([0,mid]\) 中所有边就可以缩点的情况:若存在该情况,则说明至少存在一条被缩起来的时间在该区间中的边没被算进该区间,这是 不可能的。

——Bindir0

Code

#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define mid ((l+r)>>1)
#define swap(x,y) x^=y^=x^=y
using namespace std;
const int N = 4e5+5;

char buf[1<<23],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read(){
    int s=0,w=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
    while(isdigit(ch))s=s*10+(ch^48),ch=getchar();
    return s*w;
}
bool chkmin(int &a,int b){return a>b?a=b,1:0;}

struct Edge{int u,v,t;}edge[N],e1[N],e2[N];
struct Item{int op,a,b,t;}item[N],seq[N];
struct EDGE{int nex,to;}edg[N];
struct Node{int f,s,d;}stk[N];
struct Tree{int ls,rs,siz,sum;}t[N*30];
int a[N],ori[N],dep[N],f[N],rot[N],v[N];
int head[N],dfn[N],low[N],sk[N],vis[N],ans[N];
int n,m,q,tot,siz,top,tim,tp,elen,cnt;
map<pii,int>mp;

int find(int x){return f[x]==x?x:find(f[x]);}
bool merge(int x,int y){
    x=find(x),y=find(y);
    if(x==y)return false;
    if(dep[x]<dep[y])swap(x,y);
    stk[++top]=(Node){x,y,dep[x]};
    f[y]=x,dep[x]+=dep[x]==dep[y];
    return true;
}
void del(int x){
    f[stk[x].s]=stk[x].s;
    dep[stk[x].f]=stk[x].d;
}
void addedge(int u,int v){
    edg[++elen]=(EDGE){head[u],v},head[u]=elen;
}
void clear(int cnt){
    elen=tim=0;
    for(int i=1;i<=cnt;++i)//只重置本层新加入的
        head[v[i]]=dfn[v[i]]=low[v[i]]=0;
}
void tarjan(int u){
    dfn[u]=low[u]=++tim,vis[sk[++tp]=u]=1;
    for(int e=head[u],v;v=edg[e].to,e;e=edg[e].nex) 
        if(!dfn[v])tarjan(v),chkmin(low[u],low[v]);
        else if(vis[v])chkmin(low[u],dfn[v]);
    if(low[u]==dfn[u]){
        while(sk[tp]^u)merge(u,sk[tp]),vis[sk[tp--]]=false;
        vis[u]=false,tp--;
    }
}
void solve(int L,int R,int l,int r){
    if(L>R)return ;
    if(l==r){
        for(int i=L;i<=R;++i)edge[i].t=l;
        return ;
    }
    int cnt=0,now=top,c1=0,c2=0;
    for(int i=L;i<=R;++i)if(edge[i].t<=mid)v[++cnt]=find(edge[i].u),v[++cnt]=find(edge[i].u); clear(cnt);
    for(int i=L;i<=R;++i)if(edge[i].t<=mid)addedge(find(edge[i].u),find(edge[i].v));
    for(int i=1;i<=cnt;++i)if(!dfn[v[i]])tarjan(v[i]);
    for(int i=L;i<=R;++i)
        if(edge[i].t<=mid&&find(edge[i].u)==find(edge[i].v))
            e1[++c1]=edge[i];
        else e2[++c2]=edge[i];
    for(int i=1;i<=c1;++i)edge[L+i-1]=e1[i];
    for(int i=1;i<=c2;++i)edge[L+c1+i-1]=e2[i];
    solve(L+c1,R,mid+1,r);
    while(top>now)del(top--);
    solve(L,L+c1-1,l,mid);
}
void alter(int l,int r,int& rt,int id,int v){
    if(!rt)rt=++siz;
    t[rt].siz+=v,t[rt].sum+=v*ori[id];
    if(l==r)return ;
    if(id<=mid)alter(l,mid,t[rt].ls,id,v);
    else alter(mid+1,r,t[rt].rs,id,v);
}
int query(int l,int r,int rt,int k){
    if(k>t[rt].siz)return t[rt].sum;
    if(l==r)return ori[l]*k;
    if(k<=t[t[rt].rs].siz)return query(mid+1,r,t[rt].rs,k);
    return t[t[rt].rs].sum+query(l,mid,t[rt].ls,k-t[t[rt].rs].siz);
}
int merge(int l,int r,int p,int q){
    if(!p||!q)return p+q;
    if(l==r)return t[p].siz+=t[q].siz,t[p].sum+=t[q].sum,p;
    t[p].ls=merge(l,mid+0,t[p].ls,t[q].ls),
    t[p].rs=merge(mid+1,r,t[p].rs,t[q].rs);
    t[p].siz=t[t[p].ls].siz+t[t[p].rs].siz,
    t[p].sum=t[t[p].ls].sum+t[t[p].rs].sum;
    return p;
}
bool cmp(Item a,Item b){
    return a.t==b.t?a.op<b.op:a.t<b.t;
}

signed main(){
    n=read(),m=read(),q=read();
    for(int i=1;i<=n;++i)a[i]=read(),ori[++tot]=a[i],f[i]=i,dep[i]=1;
    for(int i=1;i<=m;++i)edge[i].u=read(),edge[i].v=read(),mp[pii(edge[i].u,edge[i].v)]=i;
    for(int i=1;i<=q;++i){
        item[i].op=read(),item[i].a=read(),item[i].b=read(),item[i].t=q-i+1;
        if(item[i].op==1)edge[mp[pii(item[i].a,item[i].b)]].t=item[i].t;
        if(item[i].op==2)a[item[i].a]+=item[i].b,ori[++tot]=a[item[i].a];
    }
    // for(int i=1;i<=tot;++i)cout<<ori[i]<<" ";puts("");
    // for(int i=1;i<=m;++i)cout<<edge[i].t<<" ";puts("");
    sort(ori+1,ori+1+tot),tot=unique(ori+1,ori+1+tot)-1-ori,solve(1,m,0,q+1);
    // for(int i=1;i<=m;++i)cout<<edge[i].t<<" ";puts("");
    for(int i=1;i<=n;++i)alter(1,tot,rot[i],lower_bound(ori+1,ori+1+tot,a[i])-ori,1);
    for(int i=1;i<=q;++i)if(item[i].op^1)seq[++cnt]=item[i];
    for(int i=1;i<=m;++i)seq[++cnt]=(Item){1,edge[i].u,edge[i].v,edge[i].t};
    sort(seq+1,seq+1+cnt,cmp);

    for(int i=1;i<=n;++i)f[i]=i,dep[i]=1;
    for(int i=1;i<=cnt;++i){
        int x=seq[i].a,y=seq[i].b,fa;
        // cout<<x<<" "<<y<<" "<<seq[i].t<<" "<<seq[i].op<<endl;
        if(seq[i].op==1){
            x=find(x),y=find(y);
            if(x==y)continue;
            if(dep[x]<dep[y])swap(x,y);
            dep[x]+=dep[x]==dep[y],f[y]=x;
            rot[x]=merge(1,tot,rot[x],rot[y]);
        }else if(seq[i].op==2)
            fa=find(x),
            alter(1,tot,rot[fa],lower_bound(ori+1,ori+1+tot,a[x])-ori,-1),a[x]-=y,
            alter(1,tot,rot[fa],lower_bound(ori+1,ori+1+tot,a[x])-ori,1);
        else ans[q-seq[i].t+1]=query(1,tot,rot[find(x)],y);
    }
    for(int i=1;i<=q;++i)if(item[i].op==3)printf("%lld\n",ans[i]);
    return 0;
}
posted @ 2022-01-24 16:32  _Famiglistimo  阅读(25)  评论(0编辑  收藏  举报