【洛谷P5443】桥梁

题目

题目链接:https://www.luogu.com.cn/problem/P5443
圣彼得堡位于由 \(m\) 座桥梁连接而成的 \(n\) 个岛屿上。岛屿用 \(1\)\(n\) 的整数编号,桥梁用 \(1\)\(m\) 的整数编号。每座桥连接两个不同的岛屿。有些桥梁是在彼得大帝时代建造的,其中一些是近期建造的。这导致了不同的桥梁可能有不同的重量限制。更具体地,只有重量不超过 \(d_i\) 的汽车才能通过第 \(i\) 座桥梁。有时圣彼得堡的一些桥梁会进行翻新,但这并不一定会使桥梁承重变得更好,也就是说,进行翻新的桥梁的 \(d_i\) 可能会增加或减少。你准备开发一个产品,用于帮助公民和城市客人。目前,你开发的模块要能执行两种类型的操作:

  1. 将桥梁 \(b_j\) 的重量限制改为 \(r_j\)
  2. 统计一辆重为 \(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;
}
posted @ 2021-05-23 23:40  stoorz  阅读(110)  评论(0编辑  收藏  举报