ccpc长春-K. Ragdoll(启发式合并、并查集、gcd转化)map的神奇tle

K. Ragdoll

菜死我得了
题意:给定一组点以及他们的权重( 1 ⩽ n ⩽ 1 0 5 1\leqslant n\leqslant10^5 1n105),起初所有点都是独立的点。给定三种操作:

  1. 1   x   v 1\ x\ v 1 x v 加下标为 x x x点权为 v v v的点( n < x ⩽ n + m n<x\leqslant n+m n<xn+m, 1 ⩽ v ⩽ 2 × 1 0 5 1\leqslant v\leqslant2\times10^5 1v2×105
  2. 2   x   y 2\ x\ y 2 x y 合并 x , y x,y xy的点集
  3. 3   x   v 3\ x\ v 3 x v x x x的点权为 v v v

m m m次操作( 1 ⩽ m ⩽ 2 × 1 0 5 1\leqslant m\leqslant 2\times10^5 1m2×105),要求每次输出相同点集内所有 i , j i,j ij的个数,满足 a i ⊕ a j = g c d ( a i , a j ) a_i\oplus a_j=gcd(a_i,a_j) aiaj=gcd(ai,aj)

思路:直接暴力for循环必然t,观察要求约束的式子 a i ⊕ a j = g c d ( a i , a j ) a_i\oplus a_j=gcd(a_i,a_j) aiaj=gcd(ai,aj)
已知 a i ⊕ a j ⩾ ∣ a i − a j ∣ ⩾ g c d ( a i , a j ) a_i\oplus a_j\geqslant|a_i-a_j|\geqslant gcd(a_i,a_j) aiajaiajgcd(ai,aj),那么如果要让上述等式成立则需要满足

  1. a i ⊕ a j = ∣ a i − a j ∣ a_i\oplus a_j=|a_i-a_j| aiaj=aiaj
  2. ∣ a i − a j ∣ = g c d ( a i , a j ) |a_i-a_j|=gcd(a_i,a_j) aiaj=gcd(ai,aj)

总体的框架可以是这样:

  1. 考虑固定已知 a i a_i ai,寻找他的可行解放入 g [ a i ] g[a_i] g[ai]中,利用并查集记录 a i a_i ai所在连通块的标记 f a fa fa m p [ f a ] mp[fa] mp[fa]进行连通块内元素( a i a_i ai)的出现次数记录。
  2. 每次对连通块进行合并时利用启发式合并把时间降到 l o g n logn logn级别,把小的连通块合并到大的连通块中。
  3. 每次对于答案 a n s ans ans的维护只要在联通块改变时候对当前元素的可行解 g [ a i ] g[a_i] g[ai]进行遍历,在 m p [ f a ] mp[fa] mp[fa]中查找如果存在答案就计算其贡献。

g [ a i ] g[a_i] g[ai]可行解的预处理:枚举 a i a_i ai的每一个约数,利用上文满足式 ( 2 ) (2) 2计算 a j = g c d ( a i , a j ) ± a i a_j=gcd(a_i,a_j)\pm a_i aj=gcd(ai,aj)±ai,再对式 ( 1 ) (1) 1进行判断,合法就放入。

注意点:
不说了反正没啥注意的我就是sb
用unordered_map可以在查询时候差个常数
每次在贡献利用mp的时候需要先判断一下是否存在然后在进行加减运算,要不就t了

unordered_map<int, LL> mp[maxn];//存连通块内元素a[i]
vector<int> g[maxn];//存数字大小为下标的解
int f[maxn], sz[maxn];//并查集
int a[maxn];
LL ans;
int find(int x) {
    
    
	if (f[x] == x)return x;
	else return f[x] = find(f[x]);
}
void merge(int x, int y) {
    
    
	int fx = find(x), fy = find(y);
	if (fx == fy)return;
	else {
    
    //merge fy to fx
		if (sz[fx] < sz[fy])swap(fx, fy);
		for (auto &it : mp[fy]) {
    
    //遍历小的集合中元素,看合并后对ans是否有贡献
			//it.first要放入的元素
			for (int i = 0; i < g[it.first].size(); i++) {
    
    //查找fx中是否有放入元素的答案
				if (mp[fx].count(g[it.first][i]))ans += 1ll * it.second*mp[fx][g[it.first][i]];
			}
		}
		for (auto &it : mp[fy]) {
    
    //正式放入fy中元素进入fx中
			mp[fx][it.first] += it.second;
		}
		mp[fy].clear();
		f[fy] = fx;
		sz[fx] += sz[fy];
	}
}
void work(int x) {
    
    
	int tt;
	for (int i = 1; i*i <= x; i++) {
    
    
		if (x % i)continue;
		else {
    
    
			tt = i;
			if (((x - tt) ^ x) == gcd(x - tt, x) && (x - tt)) g[x].push_back(x - tt);
			if (((x + tt) ^ x) == gcd(x + tt, x) && (x + tt) <= 200000)g[x].push_back(x + tt);
			if (i != x / i) {
    
    
				tt = x / i;
				if (((x - tt) ^ x) == gcd(x - tt, x) && (x - tt)) g[x].push_back(x - tt);
				if (((x + tt) ^ x) == gcd(x + tt, x) && (x + tt) <= 200000)g[x].push_back(x + tt);
			}
		}
	}
}
int main()
{
    
    
	for (int i = 1; i <= 200100; i++) work(i);
	/*for (int i = 1; i <= 200000; i++) {
		for (int j = i + i; j <= 200000; j += i) {
			if (gcd(j, i^j) == i)g[j].push_back(i^j);
		}
	}*/
	int n, m;
	int fl, x, v, y;
	ans = 0;
	cin >> n >> m;
	for (int i = 1; i <= n + m; i++) {
    
     sz[i] = 1, f[i] = i; }
	for (int i = 1; i <= n; i++) {
    
    
		cin >> a[i]; mp[i][a[i]]++;
	}
 
	while (m--) {
    
    
		cin >> fl >> x;
		if (fl == 1) {
    
    //add point
			cin >> v;
			a[x] = v;
			sz[x] = 1;
			mp[x][a[x]] = 1;
		}
		else if (fl == 2) {
    
    //merge
			cin >> y;
			merge(x, y);
		}
		else {
    
    //change
			cin >> v;
			int u = find(x);
			for (int i = 0; i < g[a[x]].size(); i++) {
    
    //遍历ax可能的答案,把原来的贡献去掉
				if (mp[u].count(g[a[x]][i]))ans -= mp[u][g[a[x]][i]];//不加if判断会t
			}
			mp[u][a[x]]--;
			a[x] = v;
			for (int i = 0; i < g[a[x]].size(); i++) {
    
    
				if (mp[u].count(g[a[x]][i]))ans += mp[u][g[a[x]][i]];
			}
			mp[u][a[x]]++;
		}
		cout << ans << endl;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_44986601/article/details/110136250
今日推荐