Codeforces Round 1000 (Div. 2)


A. Minimal Coprime

题意:互素区间是指\(gcd(l, r) = 1\)的区间,极小互素区间是互素区间并且没有一个被他包含的区间也是互素区间。问你区间\([l, r]\)里有多少个极小互素区间。

根据数论的基础知识,\(x,x+1\)一定是互素的,所以统计所有长度为\(2\)的区间就行,不过要注意,\([1, 1]\)是唯一一个长度不为\(2\)的极小互素区间。

点击查看代码
void solve() {
    int l, r;
    std::cin >> l >> r;
    int ans = r - l;
    if (l == 1) {
    	ans = std::max(ans, 1);
    }

    std::cout << ans << "\n";
}

B. Subsequence Update

题意:给你一个数组,你可以翻转一个子序列,求一次操作后\([l, r]\)区间的最小总和。

赛时把子序列看成子数组了。。。
左边的最小的\(r-l+1\)个数一定能翻转到\([l, r]\)里。具体操作是,假设前\(r-l+1\)小的数有\(x\)个不在\([l, r]\)里面,那么\([l, r]\)里有\((r-l+1)-x\)个最小值,发现不是最小值的正好也是\((r-l+1-(r-l+1)-x)=x\)个,那么明显可以选中这些数进行翻转,这样\(x\)个数就到\([l, r]\)里了。右边同理,所以两边模拟一下取最小就行。

点击查看代码
void solve() {
    int n, l, r;
    std::cin >> n >> l >> r;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    auto b = a;
    std::sort(a.begin(), a.begin() + r);
    i64 sum1 = 0, sum2 = 0;
    for (int i = 0; i < r - l + 1; ++ i) {
    	sum1 += a[i];
    }

    std::sort(b.begin() + l - 1, b.end());
    for (int i = l - 1; i < l - 1 + r - l + 1; ++ i) {
    	sum2 += b[i];
    }

    i64 ans = std::min(sum1, sum2);

    std::cout << ans << "\n";
}

C. Remove Exactly Two

题意:给你一颗树,你要删掉两个点,然后使得剩下的连通块最大。

\(u\)的度数为\(deg_u\)。因为树没有环,那么如果删一个点就会分成\(deg_u\)块,那就是删度数最大的。现在考虑两个点,发现如果删除的两个点之间没有连边的话是不影响的,如果连边则因为有一条边重复了会少分成一个块。所以我们先枚举所有度数最大的点看有没有两个最大点之间没有连边,有点话就选这两个点就行。否则就拿一个度数最大的和一个剩下点度数最大的点就行。然后我是用并查集数的联通块,不过好像可以直接算。
这题赛时代码写的奇丑。

点击查看代码
struct DSU {
    std::vector<int> f, siz;
    
    DSU() {}
    DSU(int n) {
        init(n);
    }
    
    void init(int n) {
        f.resize(n);
        std::iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
    }
    
    int find(int x) {
        while (x != f[x]) {
            x = f[x] = f[f[x]];
        }
        return x;
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) {
            return false;
        }
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }
    
    int size(int x) {
        return siz[find(x)];
    }
};

void solve() {
    int n;
    std::cin >> n;
    std::vector<std::vector<int> > adj(n);
    std::vector<std::pair<int, int> > edges;
    std::vector<int> deg(n);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    	++ deg[u]; ++ deg[v];
    	edges.push_back({u, v});
    }

    if (n == 2) {
    	std::cout << 0 << "\n";
    	return;
    }
    int max = *std::max_element(deg.begin(), deg.end());

    std::vector<int> b;
    int x = -1, y = -1, mx = 0;
    for (int i = 0; i < n; ++ i) {
		if (deg[i] == max) {
			b.push_back(i);
		}
    }


    if (b.size() >= 2) {
    	int mx = 0;
    	for (auto & u : b) {
    		for (auto & v : b) {
    			if (u != v) {
    				int flag = 0;
    				for (auto & x : adj[u]) {
    					if (x == v) {
    						flag = 1;
    						break;
    					}
    				}

    				if (max * 2 - flag > mx) {
    					mx = max * 2 - flag;
    					x = u, y = v;
    				}
    			}
    		}

    		if (mx == 2 * max) {
    			break;
    		}
    	}
    } else {
    	x = b[0];
    	int mx = -1;
    	for (int i = 0; i < n; ++ i) {
			if (i == x) {
				continue;
			}
			int flag = 0;
    		for (auto & j : adj[i]) {
    			if (x == j) {
					flag = 1;
					break;
				}
    		}

    		if (max + deg[i] - flag > mx) {
    			mx = max + deg[i] - flag;
    			y = i;
    		}
    	}
    }


    DSU d(n);
    for (auto & [u, v] : edges) {
    	if (u != x && u != y && v != x && v != y) {
    		d.merge(u, v);
    	}
    }

    int ans = 0;
    for (int i = 0; i < n; ++ i) {
    	if (i != x && i != y && d.find(i) == i) {
    		++ ans;
    	}
    }

    std::cout << ans << "\n";
}

D. Game With Triangles

题意:给你两个数组\(A, B\),每次你可以从\(A\)选两个数和从\(B\)选一个数或者从\(B\)选两个和从\(A\)选一个,价值为选的两个数的差的绝对值。问最多操作多少次,每次的最大价值是多少。

显然我们应该学最大最小的,那么每次选\(A\)的最大最小或者\(B\)的最大最小,如果某一方没有了,那就把这个操作换成让另一个数组操作两遍就行了。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::vector<int> a(n), b(m);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    for (int i = 0; i < m; ++ i) {
    	std::cin >> b[i];
    }

    std::sort(a.begin(), a.end());
    std::sort(b.begin(), b.end());

    std::vector<int> A, B;
    for (int i = 0; i < n / 2; ++ i) {
    	A.push_back(a[n - 1 - i]- a[i]);
    }

    for (int i = 0; i < m / 2; ++ i) {
    	B.push_back(b[m - 1 - i] - b[i]);
    }

    std::vector<i64> ans;
    int x = 0, y = 0;
    i64 sum = 0;
    while (1) {
    	if (x >= A.size() && y >= B.size()) {
    		break;
    	}

    	if (n - (x * 2 + y) >= 2 && m - (x + y * 2) >= 2) {
    		if (A[x] >= B[y]) {
    			sum += A[x ++ ];
    		} else {
    			sum += B[y ++ ];
    		}
    	} else if (n - (x * 2 + y) >= 2 && m - (x + y * 2) >= 1) {
    		sum += A[x ++ ];
    	} else if (n - (x * 2 + y) >= 1 && m - (x + y * 2) >= 2) {
    		sum += B[y ++ ];
    	} else if (x * 2 + y == n && x - 1 + y * 2 + 4 <= m && y + 2 <= B.size() && x) {
    		sum -= A[ -- x];
    		A.pop_back();
    		sum += B[y ++ ];
    		sum += B[y ++ ];
    	} else if (x + y * 2 == m && x * 2 + y - 1 + 4 <= n && x + 2 <= A.size() && y) {
    		sum -= B[ -- y];
    		B.pop_back();
    		sum += A[x ++ ];
    		sum += A[x ++ ];
    	} else {
    		break;
    	}

    	ans.push_back(sum);
    }

    std::cout << ans.size() << "\n";
    for (int i = 0; i < ans.size(); ++ i) {
    	std::cout << ans[i] << " ";
    }
    std::cout << "\n";
}

E. Triangle Tree

题意:给你一棵树,对于任意两个点\(u, v\),如果它们存在祖孙关系,则\(f(u, v) = 0\), 否则看有多少个数\(x\)可以和\(dist(u, lca(u, v)), dist(v, lca(u, v))\)组成三角形。设这样的\(x\)\(cnt_x\)个,则\(f(u, v) = cnt_x\)。求\(\sum_{u=1}^{n} \sum_{v=u+1}^{n} f(u, v)\)

观察三角形的性质,如果已经确定了两条边\(x, y\),求第三条边\(z\)有多少个取值。这里假设\(x < y\),则\(y - x < z < y + x\),因为是整数域,则可以表示为\(y - x + 1 \leq z \leq y + x - 1\),得到\(z\)的合法取值有\(2x - 1\)个。也就是说,\(f(u, v) = 2\times \min(dist(u, lca(u, v)), dist(v, lca(u, v))) - 1\)
\(lca(u, v) = l\)\(d\)为点的深度,则\(f(u, v) = 2\times \min(d_u - d_l, d_v - d_l) - 1\),等价于\(2\times \min(d_u, d_v) - 2\times d_l - 1\)。那么我们可以将贡献分成三部分来算。不过考虑特殊处理\(u, v\)存在祖孙关系比较麻烦,将这种情况代入式子发现\(f(u, v) = -1\),则可以先不管这种情况,最后算完看有多少个点对会贡献-1,把这些值加起来就行。
现在观察这个式子\(f(u, v) = 2\times \min(d_u, d_v) - 2\times d_l - 1\),在\(\sum_{u=1}^{n} \sum_{v=u+1}^{n} f(u, v)\)中,\(-1\)总共算了\(\frac{n(n-1)}{2}\)次,可以直接计算。
再看\(- 2\times d_l\),每个\(d_l\)要算多少次要看它能被多少点对当作\(lca\),那么可以\(dfs\)的时候计算所有子树能贡献多少点对,可以一棵一棵子树算,每棵子树和前面已经算过的子树的子树和做乘就行,然后累加子树和。
如何计算\(2\times \min(d_u, d_v)\)?一种情况假定\(d_u\)是较小的值,那么它和所有深度大于它的节点都可以贡献一个\(d_u\)的值,可以把每个深度的个数存下来,\(dfs\)之后再单独计算。还有一种情况就是\(d_u == d_v\)这种情况相当于从所有深度为\(d_u\)的节点中选两个出来,可以用组合数计算。
最后看存在祖孙关系的点对贡献了多少个-1,直接减去\(\sum_{i=1}^{n} (size_i - 1)\)即可。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<std::vector<int> > adj(n);
    for (int i = 1; i < n; ++ i) {
    	int u, v;
    	std::cin >> u >> v;
    	-- u, -- v;
    	adj[u].push_back(v);
    	adj[v].push_back(u);
    }

    i64 ans = 0;
    std::vector<int> size(n), d(n + 1), cnt(n + 1), sum(n + 2);
    auto dfs = [&](auto self, int u, int fa) -> void {
    	cnt[d[u]] += 1;
    	size[u] = 1;
    	i64 tot = 0;
    	for (auto & v : adj[u]) {
    		if (v == fa) {
    			continue;
    		}

    		d[v] = d[u] + 1;
    		self(self, v, u);
    		tot += (i64)size[u] * size[v];
    		size[u] += size[v];
    	}

    	ans -= 2ll * d[u] * tot;
    	ans += size[u] - 1;
    };

    d[0] = 1;
    dfs(dfs, 0, -1);
    for (int i = n; i >= 1; -- i) {
    	sum[i] = cnt[i] + sum[i + 1];
    }

    for (int i = 1; i <= n; ++ i) {
    	ans += 2ll * i * ((i64)sum[i + 1] * cnt[i] + (i64)cnt[i] * (cnt[i] - 1) / 2);
    }

    ans = ans - (i64)n * (n - 1) / 2;

    std::cout << ans << "\n";
}
posted @ 2025-01-22 22:59  maburb  阅读(573)  评论(0)    收藏  举报