首页 > 其他分享 >CF938G Shortest Path Queries 题解

CF938G Shortest Path Queries 题解

时间:2023-07-27 12:34:16浏览次数:38  
标签:node return int 题解 fa ans Path Shortest dis

目录

题目链接

CF938G
洛谷挂了 只能交CF

题目分析

本题有以下几个关键点:

为什么使用生成树建树

首先 根据 \(WC2011\) 我们发现可以使用 \(dfs\) 序来保存节点之间的关系
但是我们发现 本题目中存在加边 删边操作 不适合使用 \(dfs\) 序。
但是,生成树支持这一点。我们只需要在添加这条边的时候进行对环贡献的拆解就可以。

对于异或贡献的分析

由于使用了可撤销并查集,我们在使用生成树边时候并不是针对 \((u,v,w)\) 建边,而是对 \((fa[u],fa[v],w')\) 建边。
为了使两者等价,我们不得不提前计算 \(w\) 对图新产生的贡献
然后在建边时,使用新的权值建边。
计算方式由 \(dis[i]\) 的定义产生影响:

dis[i] 表示 i 点到祖先的异或距离

所以,我们考虑如下的求新边权方式:

inline int get_fa(int x){
	if(x == f[x])	return 0 ;
	return dis[x] ^ get_fa(f[x]) ;
}

令 \(x , y\) 表示为两端节点的祖先
最后的新边权表示为:

\[w_i=dis_x^{\wedge}dis_y^{\wedge}w_i \]

注意最后的答案 是祖先分别到 \(u , v\) 的贡献,中间还要在异或一下。

code

#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
#include<cstring>
using namespace std ;
#define ll long long
const int MAXN = 2e5+10 ;
struct Edge{
	int to ,from ,w ;
}edge[MAXN << 1] ;
map<ll , int>	mapp ;
int ans[MAXN][3] ;
vector<ll> que[MAXN<<2] ;
vector<ll> upd[MAXN<<2] ;//开线段树大小 
int ask[MAXN] ,rk[MAXN] ,t[MAXN<<1] ,f[MAXN] ,dis[MAXN] ,len ,s[MAXN<<1] ;

inline void modify1(int node , int li , int ri , int l , int r , int id){
	if(li > r || ri < l)	return ;
	if(l <= li && ri <= r){
		upd[node].push_back(id) ;//是新边,编号为1~len,放在node节点上 
		return ;
	}
	int mid = li + ri >> 1 ;
	modify1(node << 1 , li , mid , l , r , id) ;
	modify1(node << 1 | 1 , mid+1 , ri , l , r , id) ;
}

inline void modify2(int node , int li , int ri , int x , int id){
	if(li > x || ri < x)	return ;
	if(li == ri){
		que[node].push_back(id) ;//等待查询的队列中 
		return ;
	} 
	int mid = li + ri >> 1 ;
	modify2(node << 1 , li , mid , x , id) ;
	modify2(node << 1 | 1 , mid+1 , ri , x , id) ;
}

inline void ins(int x , int *p){//插入线性基 
	for(int i = 29;i >= 0;--i)
		if(x & (1 << i)){
			if(p[i] == 0){
				p[i] = x ;
				return ;
			}else{
				x ^= p[i] ;
			}	
		}
}

inline int query(int val ,int *p){//寻找最小xor和 
	int ans = val ;
	for(int i = 29;i >= 0;--i){
		ans = min(ans , ans ^ p[i]) ;
	}
	return ans ;
}

inline int findd(int x){
	return x== f[x] ? x : findd(f[x]) ;
}

inline int get_fa(int x){//找到从祖先到当前节点的异或和,从而可以确定要建新边的权值 
	if(x == f[x])	return 0 ;
	return dis[x] ^ get_fa(f[x]) ;
}

int tp ;
int px[MAXN] ,pd[MAXN] ,pr[MAXN] ;
inline int update(int node , int *p){
	int siz = 0 ;
	for(int i = 0;i < upd[node].size();++i){
		int id = upd[node][i] ;//加边操作编号 
		int u = edge[id].from ,v = edge[id].to ,w = edge[id].w ;
		int x = findd(u) ,y = findd(v) ;
		w ^= (get_fa(u) ^ get_fa(v)) ;//计算dis[i]:两端的祖先节点异或上本边权 
		//为什么不是父亲节点?因为在并查集里我们实际上建边的是x与y而非u与v 
		if(rk[x] > rk[y])	swap(x , y) ;//按秩合并 
		if(x != y){//是生成树中新的边 
			tp++ ;
			px[tp] = x ;//在栈里面储存要加边的点的信息以便删除 
			pd[tp] = dis[x] ;
			dis[x] = w ;//加边 
			f[x] = y ;	
			pr[tp] = rk[y] ;
			if(rk[x] == rk[y])	rk[y]++ ;//按秩合并 
			siz++ ;
		}else{//产生环,放入线性基 
			ins(w , p) ;
		}
	}
	return siz ;
}

inline void clear(int node , int siz){//撤销并查集操作 
	while(siz--){
		int x = px[tp] ,y = f[x] ;
		f[x] = x ;
		dis[x] = pd[tp] ;
		rk[y] = pr[tp] ;
		tp-- ;
	}
}

inline void solve(int node , int l , int r , int *p){
	int siz = update(node , p) ;//查询前先更新一下 设置好当前环境下边的状态 
	if(l == r){
		for(int i = 0;i < que[node].size();++i){//由于查询的时候是按照时间点查询的,所以不会产生一次询问分裂成两个节点的情况 
			int id = que[node][i] ; 
			ask[id] = query(get_fa(ans[id][1]) ^ get_fa(ans[id][2]) , p) ;//基础的必需值和线性基增加的环值贡献 
		}
		clear(node , siz) ;
		return ;
	}
	int mid = l + r >> 1 ;
	int h[30] ;
	for(int i = 0;i <= 29;++i)	h[i] = p[i] ;
	solve(node << 1 , l , mid , p) ; 
	for(int i = 0;i <= 29;++i)	h[i] = p[i] ;//重置p[]数组 
	solve(node << 1 | 1 , mid+1 , r , p) ;
	clear(node , siz) ;//撤销 
}

int main(){
	ll n ,m ,q ;
	scanf("%lld%lld" , &n , &m) ;
	for(int i = 1;i <= m;++i){
		ll u ,v ,w ;
		scanf("%lld%lld%lld" , &u , &v , &w) ;
		if(u > v)	swap(u , v) ;//保证后面的map[]映射正确 
		mapp[u * n + v] = i ;
		edge[i].from = u ;
		edge[i].to = v ;
		edge[i].w = w ;
		s[i] = 1 ;
	}
	len = m ;
	scanf("%lld" , &q) ;
	int tim = 1 ,tot = 0 ;
	for(int i = 1;i <= q;++i){
		long long opt ,u ,v ,w ;
		scanf("%lld%lld%lld" , &opt , &u , &v) ;
		if(u > v)	swap(u , v) ;
		if(opt == 1){
			scanf("%lld" , &w) ;
			mapp[n * u + v] = ++len ;//第len号新边 
			edge[len].from = u ,edge[len].to = v ,edge[len].w = w ;//重新加入这条边 
			s[len] = ++tim ;//在第tim号时间时发生 
		}else if(opt == 2){
			t[mapp[u * n + v]] = tim++ ;//此时这条边删除(tim时刻还存在) 
		}else{//一次询问操作 
			tot++ ;
			ans[tot][0] = tim ;
			ans[tot][1] = u ;
			ans[tot][2] = v ;
		}
	}
	for(int i = 1;i <= len;++i){
		if(t[i] == 0)	t[i] = tim ;//默认一直存在 
		modify1(1 , 1 , tim , s[i] , t[i] , i) ;//统一加边 
	}
	for(int i = 1;i <= tot;++i)	modify2(1 , 1 , tim , ans[i][0] , i) ;//统一询问 
	for(int i = 1;i <= n;++i)	f[i] = i ,dis[i] = 0 ,rk[i] = 1 ;
	int p[30] ;
	memset(p , 0 , sizeof p) ;
	for(int i = 1;i <= tot;++i)	ask[i] = 1 << 30 ;//将ans[]更新为最大值 来求min 
	solve(1 , 1 , tim , p) ;//统一解决 
	for(int i = 1;i <= tot;++i)	printf("%d\n" , ask[i]) ;
	return 0 ;
}

标签:node,return,int,题解,fa,ans,Path,Shortest,dis
From: https://www.cnblogs.com/adolf-stalin/p/17584637.html

相关文章

  • k8s数据卷 Volume 之 hostPath 与 emptyDir
    一、为什么需要volume(数据卷)?容器中的文件在磁盘上是临时存放的,这给容器中运行比较重要的应用程序带来一些问题。问题1:当容器升级或者崩溃时,kubelet会重建容器,容器内文件会丢失问题2:一个Pod中运行多个容器需要共享文件。Kubernetes卷(Volume)这一抽象概念能够解决这两个问题......
  • UVA10702 Travelling Salesman 题解
     UVA10702TravellingSalesman题解题面:有个旅行的商人,他每到一个的新城市,便卖掉所有东西再购买新东西,从而获得利润。从某城市A到某城市B有固定利润(B 到A 的利润可能不同)。已知城市可以重复到达,从S 点出发,经过T 个城市,有E个城市能作为终点,求最大的利润。先定义......
  • CF1053E-Euler Tour题解
    前言还是一道神仙题很难想题面luogu上copy的样例解释懒得翻,我觉得应该都看得懂样例吧。题面翻译现有一棵\(n\)个点的形态未知的树,给定其长度为\(2n-1\)的欧拉序的一部分请根据给出的残缺的欧拉序还原出一个完整的欧拉序或判断不存在这样的树输入中用非零数字表示欧拉......
  • P3704 [SDOI2017] 数字表格 题解
    一、题目描述:用$f_i$表示斐波那契数列的第$i$项,那么有:$f_0=0,f_1=1;f_n=f_{n-1}+f_{n-2},n\ge2$现在有一个$n$行$m$列的数字表格,第$i$行第$j$列的数字是$f_{\gcd(i,j)}$。求这个表格所有数的乘积。共有$T$组数据,答案对$10^9+7$取模。......
  • Xcode12 开发12.5.7版本IOS的问题解决
    1.xcode12默认是创建的工程是14.2,所以需要修改一下工程版本。点击项目最上面的蓝色文件就可以打开下面的界面了。2.安装app之后,界面黑屏。解决方法如下:在AppDelegate.h中:#import<UIKit/UIKit.h>@interfaceAppDelegate:UIResponder<UIApplicationDelegate>//增......
  • [ARC143B] Counting Grids 题解
    CountingGrids题目大意将\(1\simn^2\)填入\(n\timesn\)的网格\(A\)中,对于每个格子满足以下条件之一:该列中存在大于它的数。该行中存在小于它的数。求方案数。思路分析首先有一个比较显然的结论:对于一个不合法的方案,有且仅有一个数不满足任何一个条件。考虑......
  • [ABC308G] Minimum Xor Pair Query 题解
    MinimumXorPairQuery题目大意维护一个序列,支持动态插入,删除,查询最小异或对。思路分析看到查询最小异或对首先想到01Trie,但01Trie不支持删除,考虑暴力套一个线段树分治。需要预处理出每个元素的存活区间,这里使用了map<int,vector<int>>。注意,有的元素会存活到最后,需要特......
  • 网络瘤24题解+总结
    目录网络流24题太空飞行计划最大权闭合子图模型最小路径覆盖问题Trick总结网络流24题顺序主观决定太空飞行计划教训:(开始想费用流,搞半天出不来)网络流解决最大/小费用问题,要么最小割最大流,要么最小费用流最小费用流的前提是最大流,所以在有一些点可以不取换最优代价的时候,是......
  • classpath的jar包中读取文件
    在idea中读取resources下的文件没有问题(调用getFile),但是打成jar包就会出问题;使用spring的ClassPathResource或者hutool的ClassPathResource去解析文件都会有问题;但是使用上面两个工具去读取inputstream或者byte就没问题,因为内部都是调用ClassLoader的getResource方法,如果是文件......
  • P5369 [PKUSC2018] 最大前缀和 题解
    传送门题目大意给定一个序列,求任意重排\(n!\)中情况所以的最大非空前缀和的和。模\(998244353\)。\(n\e20\),\(\sum|a_i|\le10^9\)题目解析考虑最大前缀和的性质,有:对于最大前缀和部分,所有的真后缀大于等于零。(最大前缀和可能小于零)对于不在最大前缀和的后半部分,所......