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;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/16580296.html
浙公网安备 33010602011771号