洛谷-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();
}

浙公网安备 33010602011771号