P5163 WD与地图

题解

Posted by WrongAnswer_90's Blog on December 27, 2023

更好的阅读体验

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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
	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');