牛客 - 红蓝图(克鲁斯卡尔重构树的dfs序上建主席树)

题目链接:点击查看

题目大意:给出一张 n 个点和 m 条边组成的无向图,每条边都有边权和颜色,颜色分为红色和蓝色,现在有 q 次相互独立的操作,每次操作会询问 ( x , t ) ,问删除掉所有权值大于 t 的红色边和所有权值小于 t 的蓝色边后,x 所在的连通块的大小是多少

注意,两个点联通,既需要存在着红色的可达路径,也需要存在着蓝色的可达路径

题目分析:如果去掉蓝色边的限制,只考虑 “删除掉权值大于 t 的边后,点 x 所在的连通块的大小是多少” ,这就是个标准的克鲁斯卡尔重构树的dfs序问题,连线段树都不用建了,直接输出 R[ x ] - L[ x ] + 1 就是答案了(dfs序代表的子树区间)

现在又有了蓝色边的限制,不难想到让红色边和蓝色边单独考虑,这样每次询问时,假设点 x 在红色边中对应子树的 dfs 序为 [ l1 , r1 ],在蓝色边中对应子树的 dfs 序为 [ l2 , r2 ],现在我们的问题转换为了,这两段区间对应到点 1 ~ n 中,交集的大小

因为树上的 dfs 序与树上的节点是一一对应的,换句话说克鲁斯卡尔重构树上的叶子节点的 dfs 序与点 1 ~ n 也是一一对应的,这样就形成了一种对应关系:蓝色树上的 dfs 序 <=> 点 1 ~ n <=> 红色树上的 dfs 序,通过点 1 ~ n 做过渡,不难看出蓝色树上的 dfs 序与红色树上的 dfs 序中有 n 个点也存在着一一对应的关系,利用主席树将其对应上就可以了

代码:
 

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<unordered_map>
using namespace std;
     
typedef long long LL;
     
typedef unsigned long long ull;
     
const int inf=0x3f3f3f3f;

const int N=1e6+100;

struct Ex_Kruskal
{
	struct Edge
	{
		int x,y,w;
		bool operator<(const Edge& t)const
		{
			return w<t.w;
		}
	}edge[N];
	int f[N],L[N],R[N],val[N],dp[N][25],id[N],tot,index,cnt,n;
	vector<int>node[N];
	void init(int n)
	{
		cnt=tot=0;
		index=n;
		for(int i=0;i<=n<<1;i++)
		{
			f[i]=i;
			node[i].clear();
		}
	}
	int find(int x)
	{
		return f[x]==x?x:f[x]=find(f[x]);
	}
	void addedge(int x,int y,int w)
	{
		edge[++cnt]={x,y,w};
	}
	void solve()
	{
		sort(edge+1,edge+1+cnt);
		for(int i=1;i<=cnt;i++)
		{
			int xx=find(edge[i].x),yy=find(edge[i].y);
			if(xx!=yy)
			{
				f[xx]=f[yy]=++index;
				node[index].push_back(xx);
				node[index].push_back(yy);
				val[index]=edge[i].w;
			}
		}
		n=index;
		for(int i=1;i<=n;i++)
			if(find(i)==i)
				dfs(i,0);
	}
	int get_pos(int x,int limit)
	{
		for(int i=20;i>=0;i--)
			if(dp[x][i]&&val[dp[x][i]]<=limit)
				x=dp[x][i];
		return x;
	}
	void dfs(int u,int fa)
	{
		L[u]=++tot;
		id[tot]=u;
		dp[u][0]=fa;
		for(int i=1;i<=20;i++)
			dp[u][i]=dp[dp[u][i-1]][i-1];
		for(auto v:node[u])
			dfs(v,u);
		R[u]=tot;
	}
}t1,t2;

struct Node
{
	int l,r;
	int sum;
}tree[N*20];

int cnt,root[N];

void update(int num,int &k,int l,int r)
{
	tree[cnt++]=tree[k];
	k=cnt-1;
	tree[k].sum++;
	if(l==r)
		return;
	int mid=l+r>>1;
	if(num<=mid)
		update(num,tree[k].l,l,mid);
	else
		update(num,tree[k].r,mid+1,r);
}

int query(int i,int j,int l,int r,int L,int R)//[l,r]:目标区间 [L,R]:当前区间 
{
	if(L>r||R<l)
		return 0;
	if(L>=l&&R<=r)
		return tree[j].sum-tree[i].sum;
	int mid=L+R>>1;
	return query(tree[i].l,tree[j].l,l,r,L,mid)+query(tree[i].r,tree[j].r,l,r,mid+1,R);
}

void init()
{
	root[0]=0;
	tree[0].l=tree[0].r=tree[0].sum=0;
	cnt=1;
}

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.in.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	init();
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	t1.init(n),t2.init(n);
	for(int i=1;i<=m;i++)
	{
		int x,y,c;
		scanf("%d%d%d",&x,&y,&c);
		x++,y++;
		if(c==0)//red
			t1.addedge(x,y,i);
		else//blue
			t2.addedge(x,y,-i);
	}
	t1.solve(),t2.solve();
	for(int i=1;i<=t2.n;i++)//遍历blue_tree的dfs序 
	{
		root[i]=root[i-1];
		if(t2.id[i]<=n)//i是t2的dfs序,t2.id[i]是在dfs序上对应的节点,t1.L[t2.id[i]]是对应在t1上的dfs序 
			update(t1.L[t2.id[i]],root[i],1,t1.n);
	}
	while(q--)
	{
		int x,t;
		scanf("%d%d",&x,&t);
		x++;
		int pos1=t1.get_pos(x,t),pos2=t2.get_pos(x,-t);
		int l1=t1.L[pos1],r1=t1.R[pos1];//对应在t1上的dfs序范围 
		int l2=t2.L[pos2],r2=t2.R[pos2];//对应在t2上的dfs序范围
		printf("%d\n",query(root[l2-1],root[r2],l1,r1,1,t1.n));
	}























    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45458915/article/details/109113130
今日推荐