题目链接:https://www.luogu.com.cn/problem/P5443
圣彼得堡位于由 \(m\) 座桥梁连接而成的 \(n\) 个岛屿上。岛屿用 \(1\) 到 \(n\) 的整数编号,桥梁用 \(1\) 到 \(m\) 的整数编号。每座桥连接两个不同的岛屿。有些桥梁是在彼得大帝时代建造的,其中一些是近期建造的。这导致了不同的桥梁可能有不同的重量限制。更具体地,只有重量不超过 \(d_i\) 的汽车才能通过第 \(i\) 座桥梁。有时圣彼得堡的一些桥梁会进行翻新,但这并不一定会使桥梁承重变得更好,也就是说,进行翻新的桥梁的 \(d_i\) 可能会增加或减少。你准备开发一个产品,用于帮助公民和城市客人。目前,你开发的模块要能执行两种类型的操作:
- 将桥梁 \(b_j\) 的重量限制改为 \(r_j\)。
- 统计一辆重为 \(w_j\) 的汽车从岛屿 \(s_j\) 出发能够到达多少个不同的岛屿。
请你回答所有第二种操作的答案。
\(n\leq 5\times 10^4;m,Q\leq 10^5\)。
吐槽:同一份代码,块长为 \(\sqrt{Q}\) 可以获得 \(4\)pts 的高分,块长调到 \(1400\) 吸手氧就过了。
总之就是非常卡常,同时块长 \(1400\) 不吸氧也只能拿到 \(8\)pts。虽然我实现非常垃圾,但是我还是不相信这个玩意可以在 APIO 的土豆评测机上跑过去。
这个修改很烦,如果没有修改就是 Kruscal 重构树的傻逼题。
对询问和操作分块,设块长为 \(B\),考虑如何求出一个块里的所有询问的答案。
发现这一个块中的修改次数是 \(O(B)\) 的,那我们可以把不需要修改的边先搞出来,然后枚举所有询问再把这个块内的修改搞掉。
把所有不在这个块中修改的边按照边权从大到小排序,再把这个快中的所有询问按照车的重量从大到小排序,然后依次枚举每一个询问,可以用指针扫描不需要修改的边,并把边权大于等于这次询问的车的重量的边加进并查集中。
然后枚举这个块内的每一个修改,如果这个修改在询问的前面,并且此次修改后,询问之前,修改的这条边不会再被修改,且修改后的边权不小于车的质量,那么就把边加入并查集中。
这样做的好处是我们对于每一个块的询问,初始边一共只会枚举 \(O(m)\) 次,对于每一个询问单独枚举的修改的边只有 \(O(B)\)。
进行完一个询问后需要把区间内的修改复原,使用可撤销并查集即可实现。
时间复杂度 \(O(\frac{Q}{B}m\log m+QB\log n)\),理论上取 \(B=\sqrt{n}\) 最优,实际上可能需要调调参。
#include <bits/stdc++.h>
#define mp make_pair
using namespace std;
const int N=100010;
int n,n1,n2,n3,m,Q,B,U[N],V[N],D[N],id[N],father[N],siz[N],ans[N];
bool vis[N];
stack<pair<int,int> > st;
struct Query
{
int opt,x,y,id;
}ask[N],qry[N],upd[N];
bool cmp1(int x,int y)
{
return D[x]<D[y];
}
bool cmp2(Query x,Query y)
{
return x.y>y.y;
}
void prework()
{
n1=n2=n3=0;
for (int i=1;i<=m;i++) vis[i]=0;
for (int i=1;i<=n;i++) father[i]=i,siz[i]=1;
}
int find(int x)
{
return x==father[x]?x:find(father[x]);
}
void merge(int x,int y,bool flag)
{
if (x==y) return;
if (siz[x]>siz[y]) swap(x,y);
father[x]=y; siz[y]+=siz[x];
if (flag) st.push(mp(x,y));
}
int main()
{
B=1400;
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
scanf("%d%d%d",&U[i],&V[i],&D[i]);
scanf("%d",&Q);
for (int i=1;i<=Q;i++)
{
scanf("%d%d%d",&ask[i].opt,&ask[i].x,&ask[i].y);
ask[i].id=i;
}
for (int i=1;i<=Q/B+1;i++)
{
prework();
int L=(i-1)*B+1,R=min(i*B,Q);
for (int j=L;j<=R;j++)
if (ask[j].opt==1)
upd[++n1]=ask[j],vis[ask[j].x]=1;
else
qry[++n2]=ask[j];
for (int j=1;j<=m;j++)
if (!vis[j]) id[++n3]=j;
sort(id+1,id+1+n3,cmp1);
sort(qry+1,qry+1+n2,cmp2);
for (int j=1,k=n3;j<=n2;j++)
{
for (int l=1;l<=n1;l++) vis[upd[l].x]=1;
for (;k && D[id[k]]>=qry[j].y;k--)
merge(find(U[id[k]]),find(V[id[k]]),0);
for (int l=n1;l>=1;l--)
if (upd[l].id<qry[j].id && vis[upd[l].x])
{
vis[upd[l].x]=0;
if (upd[l].y>=qry[j].y)
merge(find(U[upd[l].x]),find(V[upd[l].x]),1);
}
for (int l=1;l<=n1;l++)
if (vis[upd[l].x])
{
vis[upd[l].x]=0;
if (D[upd[l].x]>=qry[j].y)
merge(find(U[upd[l].x]),find(V[upd[l].x]),1);
}
ans[qry[j].id]=siz[find(qry[j].x)];
for (;st.size();st.pop())
{
int x=st.top().first,y=st.top().second;
siz[y]-=siz[x]; father[x]=x;
}
}
for (int j=1;j<=n1;j++)
D[upd[j].x]=upd[j].y;
}
for (int i=1;i<=Q;i++)
if (ans[i]) printf("%d\n",ans[i]);
return 0;
}