• 【洛谷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) 出发能够到达多少个不同的岛屿。

    请你回答所有第二种操作的答案。
    (nleq 5 imes 10^4;m,Qleq 10^5)

    思路

    吐槽:同一份代码,块长为 (sqrt{Q}) 可以获得 (4)pts 的高分,块长调到 (1400) 吸手氧就过了。
    总之就是非常卡常,同时块长 (1400) 不吸氧也只能拿到 (8)pts。虽然我实现非常垃圾,但是我还是不相信这个玩意可以在 APIO 的土豆评测机上跑过去。

    这个修改很烦,如果没有修改就是 Kruscal 重构树的傻逼题。
    对询问和操作分块,设块长为 (B),考虑如何求出一个块里的所有询问的答案。
    发现这一个块中的修改次数是 (O(B)) 的,那我们可以把不需要修改的边先搞出来,然后枚举所有询问再把这个块内的修改搞掉。
    把所有不在这个块中修改的边按照边权从大到小排序,再把这个快中的所有询问按照车的重量从大到小排序,然后依次枚举每一个询问,可以用指针扫描不需要修改的边,并把边权大于等于这次询问的车的重量的边加进并查集中。
    然后枚举这个块内的每一个修改,如果这个修改在询问的前面,并且此次修改后,询问之前,修改的这条边不会再被修改,且修改后的边权不小于车的质量,那么就把边加入并查集中。
    这样做的好处是我们对于每一个块的询问,初始边一共只会枚举 (O(m)) 次,对于每一个询问单独枚举的修改的边只有 (O(B))
    进行完一个询问后需要把区间内的修改复原,使用可撤销并查集即可实现。
    时间复杂度 (O(frac{Q}{B}mlog m+QBlog 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
    ",ans[i]);
    	return 0;
    }
    
  • 相关阅读:
    QTP的那些事连接oracle的方法
    QTP的那些事判定页面是否存在某个文本内容
    java计算的小数加减法计算有错误解决
    自由者-Hdsome安身立鸣博客园
    转:经典URL重写
    常见的类整理
    TD tree体验
    《大道至简》读后感
    SqlDataReader使用
    ExecuteScalar
  • 原文地址:https://www.cnblogs.com/stoorz/p/14802619.html
Copyright © 2020-2023  润新知