peiwenjun's blog 没有知识的荒原

P5163 WD与地图 题解

题目描述

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

  • 1 u v :断开 \(u\to v\) 的边,保证操作前这条边存在。
  • 2 u v :给 \(w_u\) 加上 \(v\)
  • 3 u v :求 \(u\) 所在强连通分量中权值前 \(k\) 大的点的权值和,如果不足 \(k\) 个点则输出所有点的权值和。

数据范围

  • \(1\le n\le 10^5,1\le m,q\le2\cdot10^5\) ,保证任意时刻 \(w_i\in [1,10^9]\)

时间限制 \(\texttt{3s}\) ,空间限制 \(\texttt{500MB}\)

分析

删边难以维护,时光倒流变成加边。

如果边是无向边,那么这道题会变得非常简单,并查集 + 线段树合并即可。

有向边难在哪儿呢?加入这条边并不一定会把两个强连通分量 merge 成同一个。

动态维护强连通分量并不现实,但如果知道每条边被缩起来的最晚时间,此后就可以当成无向图做了。

注:下面的时间轴没有经过翻转。

先考虑对特定的一条边 \((u,v)\) ,如何计算最晚被缩时间。

二分答案,每次加入删除时间 \(\gt mid\) 的边,然后缩点,如果 \(u,v\) 在同一个强连通分量中,就意味着 check 成功。

现在有很多条边需要计算,容易想到整体二分

目前的 check 函数仍然是 \(\mathcal O(m+q)\) 的,无法接受。根据整体二分的套路,我们希望 check 的代价与最晚被缩时间 \(\in[l,r]\) 的边数同阶。

先讲做法。维护一个可撤销并查集,每个集合表示加入 \(\gt mid\) 的边以后的一个强连通分量,然后执行以下流程:

  • 加入删除时间 \(\gt mid\) 的边,跑 tarjan 缩点。
  • 将强连通分量信息压缩到并查集中。
  • 对于每条边,如果删除时间 \(\le mid\) 或两端点不属于同一强连通分量,则答案在 \([l,mid]\) 中,否则在 \([mid+1,r]\) 中。
  • 删除图中所有边,只保留并查集的信息。
  • 递归 \([l,mid]\)
  • 撤销这一层对并查集的修改,递归 \([mid+1,r]\)

\(\text{Q}\) :为什么只需要保留并查集的信息?强联通分量之间的连边对后续为什么没有影响?

\(\text{A}\) :递归区间 \([l,r]\) 时我们加入了所有最晚被缩时间 \(\gt r\) 的边(体现在并查集中),前若干层强联通分量之间的边都储存于 vector 中,在当前层重新尝试缩点。

时间复杂度 \(\mathcal O(m\log^2n+q\log V)\)

\(200\) 行代码凑合看看吧。

#include<bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int maxn=2e5+5,inf=1e9;
int m,n,q,cnt,sum;
int u[maxn],v[maxn],opt[maxn];
map<pii,int> id;
int c[2*maxn],w[maxn];
vector<int> g[maxn],poi[maxn];
int dfn[maxn],low[maxn];
stack<int> st;
bool ins[maxn];
vector<ll> res;
struct edge
{
    int u,v,t;///t为删除时间
}e[maxn];
vector<edge> now,vec[maxn];
namespace dsu
{
    int top;
    int f[maxn],sz[maxn];
    pii st[maxn];
    int find(int x)
    {
        if(f[x]==x) return x;
        return find(f[x]);
    }
    void merge(int u,int v)
    {
        u=find(u),v=find(v);
        if(u==v) return ;
        if(sz[u]>sz[v]) swap(u,v);
        f[u]=v,sz[v]+=sz[u],st[++top]=mp(u,v);
    }
    void del(int lim)
    {
        while(top>lim)
        {
            int u=st[top].fi,v=st[top].se;
            f[u]=u,sz[v]-=sz[u],top--;
        }
    }
}
using dsu::find;
using dsu::merge;
void tarjan(int u)
{
    dfn[u]=low[u]=++cnt;
    st.push(u),ins[u]=true;
    for(auto v:g[u])
    {
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(ins[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        int v;
        poi[++sum].clear();
        do v=st.top(),st.pop(),ins[v]=false,poi[sum].push_back(v);
        while(v!=u);
    }
}
void solve(int l,int r,vector<edge> now)
{
    if(now.empty()) return ;
    if(l==r)
    {
        for(auto p:now) vec[l].push_back(p);
        return ;
    }
    int mid=(l+r)/2,num=0;
    for(auto p:now)
    {
        int u=find(p.u),v=find(p.v);
        if(p.t<=mid) continue;
        c[++num]=u,c[++num]=v,g[u].push_back(v);
    }
    sort(c+1,c+num+1);
    num=unique(c+1,c+num+1)-c-1;
    for(int i=1;i<=num;i++) if(!dfn[c[i]]) tarjan(c[i]);
    int lim=dsu::top;
    for(int i=1;i<=sum;i++)
    {
        int u=poi[i][0];
        for(auto v:poi[i]) merge(u,v);
    }
    cnt=sum=0;
    for(int i=1;i<=num;i++) g[c[i]].clear(),dfn[c[i]]=low[c[i]]=0;
    vector<edge> v[2];
    for(auto p:now)
    {
        if(p.t<=mid||find(p.u)!=find(p.v)) v[0].push_back(p);
        else v[1].push_back(p);
    }
    solve(l,mid,v[0]);
    dsu::del(lim);
    solve(mid+1,r,v[1]);
}
namespace sgmt
{
    int tot;
    int rt[maxn];
    struct node
    {
        int ls,rs,cnt;
        ll sum;
    }f[60*maxn];
    void pushup(int p)
    {
        f[p].cnt=f[f[p].ls].cnt+f[f[p].rs].cnt;
        f[p].sum=f[f[p].ls].sum+f[f[p].rs].sum;
    }
    void insert(int &p,int l,int r,int pos,int val)
    {
        if(!p) p=++tot;
        if(l==r)
        {
            f[p].cnt+=val,f[p].sum+=val*l;
            return ;
        }
        int mid=(l+r)/2;
        if(pos<=mid) insert(f[p].ls,l,mid,pos,val);
        else insert(f[p].rs,mid+1,r,pos,val);
        pushup(p);
        if(!f[p].cnt&&!f[p].sum) p=0;
    }
    ll query(int p,int l,int r,int k)
    {///[l,r]中的前k大和
        if(l==r) return 1ll*min(f[p].cnt,k)*l;
        int mid=(l+r)/2;
        if(f[f[p].rs].cnt>=k) return query(f[p].rs,mid+1,r,k);
        return query(f[p].ls,l,mid,k-f[f[p].rs].cnt)+f[f[p].rs].sum;
    }
    int merge(int x,int y,int l,int r)
    {
        if(!x||!y) return x+y;
        if(l==r)
        {
            f[x].cnt+=f[y].cnt,f[x].sum+=f[y].sum;
            return x;
        }
        int mid=(l+r)/2;
        f[x].ls=merge(f[x].ls,f[y].ls,l,mid);
        f[x].rs=merge(f[x].rs,f[y].rs,mid+1,r);
        pushup(x);
        return x;
    }
}
using sgmt::rt;
using sgmt::insert;
int main()
{
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=n;i++) scanf("%d",&w[i]);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&e[i].u,&e[i].v),e[i].t=q+1;
        id[mp(e[i].u,e[i].v)]=i;
    }
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d%d",&opt[i],&u[i],&v[i]);
        if(opt[i]==1) e[id[mp(u[i],v[i])]].t=i;
        if(opt[i]==2) w[u[i]]+=v[i];
    }
    for(int i=1;i<=n;i++)
    {
        dsu::f[i]=i,dsu::sz[i]=1;
        insert(rt[i],1,inf,w[i],1);
    }
    for(int i=1;i<=m;i++) now.push_back(e[i]);
    solve(0,q,now);///整体二分求每条边最晚什么时候被缩起来
    for(int i=q;i>=1;i--)
    {
        for(auto p:vec[i])
        {
            int u=find(p.u),v=find(p.v);
            if(u==v) continue;
            if(dsu::sz[u]>dsu::sz[v]) swap(u,v);///并查集和线段树连边方向要保持一致
            merge(u,v),rt[v]=sgmt::merge(rt[u],rt[v],1,inf);
        }
        if(opt[i]==2)
        {
            insert(rt[find(u[i])],1,inf,w[u[i]],-1);
            w[u[i]]-=v[i];
            insert(rt[find(u[i])],1,inf,w[u[i]],1);
        }
        if(opt[i]==3) res.push_back(sgmt::query(rt[find(u[i])],1,inf,v[i]));
    }
    reverse(res.begin(),res.end());
    for(auto p:res) printf("%lld\n",p);
    return 0;
}

posted on 2022-08-12 15:51  peiwenjun  阅读(13)  评论(0)    收藏  举报

导航