VP Educational Codeforces Round 49 (Rated for Div. 2)


A. Palindromic Twist

判断对应位置的差是不是等于0或2。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    for (int i = 0, j = n - 1; i < j; ++ i, -- j) {
    	if (std::abs(s[i] - s[j]) != 2 && s[i] != s[j]) {
    		std::cout << "NO\n";
    		return;
    	}
    }

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

B. Numbers on the Chessboard

分类讨论。

点击查看代码
void solve() {
    i64 n, q;
    std::cin >> n >> q;
    i64 a = (n * n + 1) / 2;
    while (q -- ) {
    	int x, y;
    	std::cin >> x >> y;
    	if (x + y & 1) {
    		i64 d = (n / 2 + (n + 1) / 2) * (x / 2);
    		if (x % 2 == 0) {
    			d -= (n + 1) / 2;
    			d += (y + 1) / 2;
    		} else {
    			d += y / 2;
    		}

    		std::cout << a + d << "\n";
    	} else {
    		i64 d = (n / 2 + (n + 1) / 2) * (x / 2);
    		if (x % 2 == 0) {
    			d -= n / 2;
    			d += y / 2;
    		} else {
    			d += (y + 1) / 2;
    		}

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

C. Minimum Value Rectangle

题意:给你\(n\)个木棒,用其中四个构造矩形,使得周长的平方除面积最小。

设两条边长为\(x, y(x < y)\)。则要求\({(2(x + y))^2}{xy}\)最小,推理一下:\({(2(x + y))^2}{xy} = \frac{4x^2 + 4y^2 + 8xy}{xy} = 4(\frac{x}{y} + \frac{y}{x}) + 8\),那么要让\(\frac{y}{x}\)最小,我们肯定选相邻的两个值。

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

    std::ranges::sort(a);
    std::vector<i64> b;
    for (int i = 0; i + 1 < n; ++ i) {
    	if (a[i] == a[i + 1]) {
    		b.push_back(a[i]);
    		i += 1;
    	}
    }

    int m = b.size();
    i64 x = b[0], y = b[1];
    for (int i = 0; i + 1 < m; ++ i) {
		if (b[i + 1] * x < y * b[i]) {
			x = b[i], y = b[i + 1];
		}
    }

    std::cout << x << " " << x << " " << y << " " << y << "\n";
}

D. Mouse Hunt

题意:\(n\)个点每个点有一个代价和一个出边。要求选一些点使得所有点都可以到达其中一个点,且这些点的代价和最小。

进行\(tarjan\)求强连通分量后缩点成\(DAG\)图,那么图中没有出边的点代表的连通分量里选一个最小值。

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

    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    	-- a[i];
    }

    std::vector<int> dfn(n), low(n), id(n), min(n, 1e9), stk(n + 10), in_stk(n);
    int top = 0, idx = 0, cnt = 0;

    auto tarjan = [&](auto & self, int u) -> void {
    	dfn[u] = low[u] = ++ idx;
    	stk[ ++ top] = u; in_stk[u] = 1;
    	int v = a[u];
		if (!dfn[v]) {
			self(self, v);
			low[u] = std::min(low[u], low[v]);
		} else if (in_stk[v]) {
			low[u] = std::min(low[u], dfn[v]);
		}

    	if (dfn[u] == low[u]) {
    		do {
    			v = stk[top -- ];
    			in_stk[v] = 0;
    			id[v] = cnt;
    			min[cnt] = std::min(min[cnt], c[v]);
    		} while (v != u);

    		++ cnt;
    	}
    };

    for (int i = 0; i < n; ++ i) {
    	if (!dfn[i]) {
    		tarjan(tarjan, i);
    	}
    }

    std::vector<int> in(cnt);
    for (int i = 0; i < n; ++ i) {
    	if (id[i] != id[a[i]]) {
    		++ in[id[i]];
    	}
    }

    int ans = 0;
    for (int i = 0; i < cnt; ++ i) {
    	if (in[i] == 0) {
    		ans += min[i];
    	}
    }

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

E. Inverse Coloring

题意:构造一个\(01\)矩阵,有三个条件:

  1. 每行的位置和上一行对应位置要么都相同要么都不同
  2. 每列的位置和前一列对应位置要么都相同要么都不同
  3. 最大的\(0\)的连通块大小小于\(k\),最大的\(1\)连通块大小小于\(k\)

那么首先要发现,确定了第一行第一列后整个矩阵就确定了。
因为一行中连续的一段在接下来的行里也连续,一列中连续的一段在接下来的列里也连续,那么它们构成的子矩阵的元素都是相同的。那么我们需要求出行的最大连续段的方案和列的最大连续段的方案。

解法一:
\(f[i][j][k]\)为前\(i\)个元素最长连续段长度为\(j\),末尾有连续\(k\)个相等的元素的方案数。
那么我们在末尾加一个相同的元素可以转移到\(f[i + 1][(\max(j, k + 1)][k + 1]\),加一个不一样的可以转移到\(f[i + 1][\max(j, 1)][1]\)
那么长度为\(j\)的方案数\(cnt_j\)就是\(\sum_{k=1}^{j} f[n][j][k]\)
答案就是枚举\(i \times j < k\)的对数加上\(cnt_i \times cnt_j\)。然后除二。为什么除二?因为我们只是算连续相同的段,如果行列组合起来,可能一个要求相同,一个要求不相同,那么就不是一样的矩阵了。
代码省略取模类。

点击查看代码
void solve() {
    int n, K;
    std::cin >> n >> K;
    std::vector f(n + 1, std::vector<Z>(n + 1));
    f[0][0] = 1;
    for (int i = 0; i < n; ++ i) {
	    std::vector g(n + 1, std::vector<Z>(n + 1));
    	for (int j = 0; j <= i; ++ j) {
    		for (int k = 0; k <= j; ++ k) {
    			if (k + 1 <= n) {
    				g[std::max(k + 1, j)][k + 1] += f[j][k];
    			}
    			g[std::max(1, j)][1] += f[j][k];
    		}
    	}

    	f = g;
    }

   	std::vector<Z> cnt(n + 1);
   	for (int i = 1; i <= n; ++ i) {
   		for (int j = 1; j <= i; ++ j) {
   			cnt[i] += f[i][j];
   		}
   	}

    Z ans = 0;
    for (int i = 1; i <= n; ++ i) {
    	for (int j = 1; j <= n; ++ j) {
    		if (i * j < K) {
    			ans += cnt[i] * cnt[j];
    		}
    	}
    }

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

解法二:
考虑用\(f[i][j]\)表示前\(i\)个元素连续段小于等于\(j\)的方案数,这样就可以通过\(f[n][i] - f[n][i - 1]\)求出恰好为\(i\)的方案数。
那么我们考虑前面转移到了\(k\),从\([k+1, i]\)都填相同的数,那么有\(f[i][j] = \sum_{k=i-j}^{i-1} f[k][\min(j, k)]\)
至于这个算出来的结果,需要乘上2。
因为观察解法一的方程,发现会给长度为1的连续段贡献两次,而这个解法只会贡献一次。相当于解法一把格子的两种情况都算上了,所以组合的时候需要去除两个不合法的情况,而这个解法每个格子只算了一种情况,需要乘二。

点击查看代码
void solve() {
    int n, K;
    std::cin >> n >> K;
    std::vector f(n + 1, std::vector<Z>(n + 1));
    f[0][0] = 1;
    for (int i = 1; i <= n; ++ i) {
    	for (int j = 1; j <= i; ++ j) {
    		for (int k = i - j; k < i; ++ k) {
    			f[i][j] += f[k][std::min(k, j)];
    		}
    	}
    }

    std::vector<Z> cnt(n + 1);
    for (int i = 1; i <= n; ++ i) {
    	cnt[i] = f[n][i] - f[n][i - 1];
    }

    Z ans = 0;
    for (int i = 1; i <= n; ++ i) {
    	for (int j = 1; j <= n; ++ j) {
    		if (i * j < K) {
    			ans += cnt[i] * cnt[j];
    		}
    	}
    }

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

F. Session in BSU

题意:有\(n\)对数,你需要在每一对数里选一个,每个数只能被选一次,求选出的数里最大值最小。

我们给每一对数连边,那么就转化为了一个图。其中边代表需求个数,点代表选择个数,那么我们可以对每个连通块单独求,如果这个连通块里边数大于点数,则无解。否则边数等于点数,则每个点都要选,为最大值。如果边数小于点数,那么肯定是一棵树的形态,我们选次小值。

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

    std::ranges::sort(c);
    c.erase(std::unique(c.begin(), c.end()), c.end());
    int m = c.size();
    auto get = [&](int x) -> int {
    	return std::ranges::lower_bound(c, x) - c.begin();
    };

    std::vector<std::vector<int>> adj(m);
    for (int i = 0; i < n; ++ i) {
    	a[i] = get(a[i]), b[i] = get(b[i]);
    	adj[a[i]].push_back(b[i]);
    	adj[b[i]].push_back(a[i]);
    }

    int ans = 0;
    std::vector<int> st(m);
    for (int i = 0; i < m; ++ i) {
    	if (!st[i]) {
    		int size = 0, cnt = 0, max1 = -1, max2 = -1;
    		auto dfs = [&](auto & self, int u) -> void {
    			st[u] = 1;
    			++ size;
    			if (u > max1) {
    				max2 = max1;
    				max1 = u;
    			} else if (u > max2) {
    				max2 = u;
    			}

    			for (auto & v : adj[u]) {
    				++ cnt;
    				if (!st[v]) {
    					self(self, v);
    				}
    			}
    		};

    		dfs(dfs, i);
    		cnt /= 2;
    		if (size < cnt) {
    			std::cout << -1 << "\n";
    			return;
    		} else if (size == cnt) {
    			ans = std::max(ans, max1);
    		} else {
    			ans = std::max(ans, max2);
    		}
    	}
    }

    std::cout << c[ans] << "\n";
}
posted @ 2025-04-01 06:47  maburb  阅读(19)  评论(0)    收藏  举报