P5163 WD与地图
喵喵题,但其实没有那么难。
删边倒序转成加边是显然的,询问可以通过值域线段树合并实现,修改,合并,查询都是好做的。考虑如何维护动态加边的 SCC。
难点是每个时刻缩点后的图是一个 DAG,并不像无向图的搜索树一样好维护,而且新加入的边可能不会立刻构成 SCC 而是和再之后加入的边构成。
简化问题,可以通过计算出每条边被并到某个 SCC 里的时刻来维护。发现每条边只会被并到某个 SCC 里一次(之后的合并是边所在的 SCC 和其他的 SCC 合并,和这条边无关),满足单调性。并且需要对于每条边都求出这个时间,考虑整体二分。
简单的想法是,对于当前的二分区间,加入 \(l\sim mid\) 间的新边和这个区间询问(询问是指对一条边什么时候合并的询问)的所有边,跑一遍 Tarjan 求出 SCC,对于所有询问边,已经属于一个 SCC 的全部向左侧递归。对于在右侧的,先加入左侧的所有边,然后向右侧递归。
正确性:右侧的边一定不会对左侧的边造成影响,否则右侧的边就已经被合并了,应当向左递归。
但是上面的复杂度是假的,原因很显然,每次 Tarjan 的复杂度和 \(r\) 有关而不是和 \(r-l+1\) 有关,其没有利用好 SCC 的性质,加入了大量的无用边导致复杂度退化。考虑一堆边,这堆边一起构成一个 SCC,我们其实只需要记录这些点是一个 SCC 即可而不用再次加边,考虑并查集维护。
把上面的做法稍作改进:跑完 Tarjan 之后,把每个 SCC 内所有点用并查集合并在一起,然后向右侧递归。每次加边的时候加入 \((find(x)-find(y))\) 这样每次跑 Tarjan 的时候复杂度就挂在了边数上。然后向左侧递归的时候需要把并查集取消,可以用可撤销并查集实现,总复杂度 \(\mathcal O(n\log^2n)\)。(另一种实现方式是把右侧边进行重标号,可以消去并查集的一只 \(\log\),本人采用并查集写法)
注意实现细节:询问边有一部分就是新边,二分的在某个区间一个已经被删掉的询问边可能还没被加进来;注意特判开始就在 SCC 里的边和最后也没被合并的边。细节挺多的,写了一上午,但是一遍过?离谱。
也算是比较重工业的题了,代码写得很丑,见谅/wul。
int n,m,q,len,all,vis[200010],ans[200010],a[200010],numa[400010];
pii bb[200010];
tup b[200010],c[200010],le[200010],ri[200010],d[200010];
map<pii,int> hash;
vector<pii> edge;
vector<int> ver,del[200010],ve[200010];
#define id(x,y) (x-1)*100000+y
namespace RDSU
{
int fa[200010],siz[200010];
stack<int> st;
inline void init(){for(int i=1;i<=n;++i)siz[i]=1,fa[i]=i;}
inline int find(int x){while(x!=fa[x])x=fa[x];return x;}
inline bool Dmerge(int x,int y)
{
if((x=find(x))==(y=find(y)))return 0;
if(siz[x]>siz[y])st.e(y),siz[x]+=siz[y],fa[y]=x;
else st.e(x),siz[y]+=siz[x],fa[x]=y;
return 1;
}
inline void cancel(){int x=st.top();st.pop();siz[fa[x]]-=siz[x],fa[x]=x;}
}
using namespace RDSU;
namespace Segment
{
int root[200010],cnt;
struct{int ls,rs,val;ll sum;}t[16000010];
inline void update(int now)
{
t[now].val=t[t[now].ls].val+t[t[now].rs].val;
t[now].sum=t[t[now].ls].sum+t[t[now].rs].sum;
}
void change(int&now,int x,int y,int L=0,int R=len)
{
if(!now)now=++cnt;
t[now].val+=numa[x]*y,t[now].sum+=y;
if(L==R)return void();
int mid=L+((R-L)>>1);
if(x<=mid)change(t[now].ls,x,y,L,mid);
else change(t[now].rs,x,y,mid+1,R);
}
int merge(int x,int y,int L=0,int R=len)
{
if(!x||!y)return x|y;
if(L==R)return t[x].val+=t[y].val,t[x].sum+=t[y].sum,x;
int mid=L+((R-L)>>1);
t[x].ls=merge(t[x].ls,t[y].ls,L,mid);
t[x].rs=merge(t[x].rs,t[y].rs,mid+1,R);
return update(x),x;
}
int ask(int now,int x,int L=0,int R=len)
{
if(x>=t[now].sum)return t[now].val;
if(!x)return 0;
if(L==R)return numa[L]*x;
int mid=L+((R-L)>>1);
if(x<=t[t[now].rs].sum)return ask(t[now].rs,x,mid+1,R);
return ask(t[now].rs,x,mid+1,R)+ask(t[now].ls,x-t[t[now].rs].sum,L,mid);
}
void print(int p,int L=0,int R=len)
{
if(!p)return;
if(L==R)return write('(',L,',',t[p].sum,')');
int mid=L+((R-L)>>1);
print(t[p].ls,L,mid),print(t[p].rs,mid+1,R);
}
}
using namespace Segment;
namespace Connection
{
int dfn[200010],low[200010],tot,num,col[200010],ins[200010];
stack<int> st;
void tarjan(int x)
{
st.e(x),dfn[x]=low[x]=++tot,ins[x]=1;
for(auto to:ve[x])
{
if(!dfn[to])tarjan(to),low[x]=min(low[x],low[to]);
else if(ins[to])low[x]=min(low[x],dfn[to]);
}
if(dfn[x]==low[x])
{
int y;++num;
do ins[y=st.top()]=0,col[y]=num,st.pop();while(y!=x);
}
}
}
using namespace Connection;
bitset<200010> viss;
void solve(int l,int r,int L,int R)
{
if(L==R)
{
for(int i=l;i<=r;++i)
if(vis[b[i].z]!=inf)
del[L].eb(b[i].z);
return;
}
if(l>r)return;
int mid=L+((R-L)>>1),len1=0,len2=0;
for(int i=L,x,y;i<=mid;++i)
{
if(vis[d[i].z]==inf)continue;
x=find(d[i].x),y=find(d[i].y);
if(!viss[x])viss[x]=1,ver.eb(x);
if(!viss[y])viss[y]=1,ver.eb(y);
ve[x].eb(y);
}
for(int i=l;i<=r;++i)
{
if(vis[b[i].z]>mid)continue;
bb[i].fi=find(b[i].x),bb[i].se=find(b[i].y);
if(!viss[bb[i].fi])viss[bb[i].fi]=1,ver.eb(bb[i].fi);
if(!viss[bb[i].se])viss[bb[i].se]=1,ver.eb(bb[i].se);
ve[bb[i].fi].eb(bb[i].se);
}
for(auto j:ver)if(!dfn[j])tarjan(j);
vector<pii> ved;
for(int i=l;i<=r;++i)
{
if(vis[b[i].z]<=mid&&col[bb[i].fi]==col[bb[i].se])
ved.eb(bb[i].fi,bb[i].se),le[++len1]=b[i];
else ri[++len2]=b[i];
}
for(auto j:ver)low[j]=dfn[j]=col[j]=0,ve[j].clear();
ver.clear(),tot=num=0;
for(int i=L;i<=mid;++i)viss[find(d[i].x)]=viss[find(d[i].y)]=0;
for(int i=l;i<=r;++i)viss[bb[i].fi]=viss[bb[i].se]=0;
for(int i=1;i<=len1;++i)b[l+i-1]=le[i];
for(int i=1;i<=len2;++i)b[l+len1+i-1]=ri[i];
solve(l,l+len1-1,L,mid);
for(auto p:ved)Dmerge(p.fi,p.se);
solve(l+len1,r,mid+1,R);
}
inline int vfind(int x){return lower_bound(numa+1,numa+1+len,x)-numa;}
inline void Merge(int x,int y){if((x=find(x))!=(y=find(y)))root[x]=root[y]=merge(root[x],root[y]),Dmerge(x,y);}
inline bool cmp(tup t1,tup t2){return t1.z<t2.z;}
inline void mian()
{
read(n,m,q),init();
for(int i=1;i<=n;++i)read(a[i]),numa[++len]=a[i];
for(int i=1;i<=m;++i)read(b[i].x,b[i].y),hash[mp(b[i].x,b[i].y)]=b[i].z=i;
for(int i=1;i<=q;++i)
{
read(c[i].x,c[i].y,c[i].z);
if(c[i].x==1)d[++all]=b[hash[mp(c[i].y,c[i].z)]],vis[hash[mp(c[i].y,c[i].z)]]=all;
if(c[i].x==2)numa[++len]=(a[c[i].y]+=c[i].z);
}
for(int i=1;i<=m;++i)if(vis[i])vis[i]=all-vis[i]+1;
for(int i=1;i<=m;++i)ve[b[i].x].eb(b[i].y);
for(int i=1;i<=n;++i)if(!dfn[i])tarjan(i);
for(int i=1;i<=m;++i)if(col[b[i].x]!=col[b[i].y])vis[i]=inf;
tot=num=0;
for(int i=1;i<=n;++i)dfn[i]=low[i]=0,ve[i].clear(),col[i]=0;
sort(numa+1,numa+1+len),len=unique(numa+1,numa+1+len)-numa-1;
reverse(d+1,d+1+all),reverse(c+1,c+1+q),init();
if(all)solve(1,m,1,all),sort(b+1,b+1+m,cmp);
for(int i=1;i<=n;++i)change(root[i],vfind(a[i]),1);
for(int i=1;i<=m;++i)if(!vis[i])ve[b[i].x].eb(b[i].y);
for(int i=1;i<=n;++i)if(!dfn[i])tarjan(i);
for(int i=1;i<=m;++i)if(!vis[i]&&col[b[i].x]==col[b[i].y])Merge(b[i].x,b[i].y);
for(int i=1,t=0,x,y;i<=q;++i)
{
if(c[i].x==1)
{
++t;
for(auto j:del[t])
{
if((x=find(b[j].x))!=(y=find(b[j].y)))
Merge(x,y);
}
}
else if(c[i].x==2)
{
change(root[find(c[i].y)],vfind(a[c[i].y]),-1);
change(root[find(c[i].y)],vfind(a[c[i].y]-=c[i].z),1);
}
else ans[i]=ask(root[find(c[i].y)],c[i].z);
}
for(int i=q;i>=1;--i)if(c[i].x==3)write(ans[i],'\n');