洛谷-P1196 银河英雄传说

洛谷-P1196 银河英雄传说

题目大意:

​ 有30000艘战舰一字排开,给两种指令:(M,i,j)表示将第i号战舰所在的舰队整个接到第j号战舰的舰队后面;(C,i,j)表示查询编号为ij的战舰之间有多少艘战舰,若这两艘战舰不在同一列中,出-1 。

思路:

​ 题中要判断某两艘战舰是否在同一列舰队中,还有多次合并操作,很自然可以想到并查集。“并”和“查”是很好做的,然而查询ij号战舰之间有多少艘战舰把我卡住了。看了一下午题解之后我终于是悟了。

​ 如果没有(C,i,j)操作,这个题目就是水题。所以下面只解析如何实现(C,i,j)操作。

​ 多次查询两点间的数量,放在线性题目中很容易就会想到前缀和。这里也是用的前缀和。用p[x]表示从x到祖先节点的距离。那么要知道ij间有多少个战舰就只要算abs(p[i] - p[j]) - 1即可。(因为两个端点不包括所以要减1)

​ 那么,只有一种情况可以让某一个位置x到祖先节点的距离发生变化:x所在舰队接到了另一个舰队Y后面。这样的话,Y中所有战舰到祖先节点的距离不变,同时x所在舰队的每一个战舰到祖先节点的距离都要加上原来Y舰队的长度。于是乎,我们就要加一个数组来记录以x为祖先的舰队长度,记作num[x] 。

​ 在查询某个点x到祖先节点的距离时,我们可以通过查询时的遍历过程,将整个路径上的点的距离更新。

ll find(int x){
	if(f[x] == x) return x ;
	
	int fx = find(f[x]) ;
	p[x] += p[f[x]] ;
	return f[x] = fx ;
	
}

因为p是祖先节点到x的前缀,而find的递归过程是从x到祖先,再从祖先到x。而做前缀的话就是要从祖先到x的方向。所以要在回溯过程中做前缀。

完整代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const ll N = 3 * 1e5 + 1000;

ll p[N] , num[N] , f[N] ;

ll find(int x){
	if(f[x] == x) return x ;
	
	int fx = find(f[x]) ;
	p[x] += p[f[x]] ;
	return f[x] = fx ;
	
}

void solve(){
	
	char x ;
	ll u , v ;
	cin >> x >> u >> v ;
	
	int fu = find(u) ;
	int fv = find(v) ;
	
	if(x == 'M'){
		if(fu != fv){
			f[fu] = fv ;
			p[fu] += num[fv] ;
			num[fv] += num[fu] ;
		}
		return ;
	}
	if(fu != fv)cout << "-1\n" ;
	else cout << abs(p[u] - p[v]) - 1 << "\n" ;
	
}

int main(){
	ios::sync_with_stdio(false) ;
	
	for(int i = 1 ; i <= N ; i ++ ) f[i] = i ;
	for(int i = 1 ; i <= N ; i ++ ) num[i] = 1 ;
	
	ll T ; cin >> T ;
	while( T -- ) solve();
	
}
posted @ 2021-11-04 20:27  tyrii  阅读(53)  评论(0)    收藏  举报