2025牛客寒假算法基础集训营5


A. 小L的三则运算

题意:给定结果和运算符,求一个合法的式子。

分情况讨论即可。

点击查看代码
void solve() {
	i64 x;    
	char c;
	std::cin >> x >> c;
	if (c == '+') {
		std::cout << 1 << " " << x - 1 << "\n";
	} else if (c == '-') {
		std::cout << x + 1 << " " << 1 << "\n";
	} else if (c == '*') {
		std::cout << 1 << " " << x << "\n";
	}
}

B. 小L出师了

题意:在\(n\)个元素里选\(k\)个元素切断序列,求剩下的段里最多有多少长度大于等于\(t\)的。

显然我们应该每隔\(t\)个选一个元素,那么就是\(t + 1\)个为一组,假设最多分\(x\)组,则有\((t + 1) \times x + k - x = n\),得\(x = (n - k) / t\)。然后\(k\)个元素最多分成\(k + 1\)段,两个取最小

点击查看代码
void solve() {
    i64 n, t, k;
    std::cin >> n >> t >> k;
    std::cout << std::min((n - k) / t, k + 1) << "\n";
}

C. 小L的位运算

题意:给你三个二进制串\(a, b, c\),你可以花\(x\)花费更改\(a, b\)上任意一位的值,也可以花\(y\)花费交换\(a\)上或\(b\)上两位的值。求\(a \oplus b = c\)的最小花费。

先把异或值不等于\(c\)的位置都存下来,可以分四类,根据这一位\(a\)的值和\(b\)的值分为\(00, 01, 10, 11\)。然后发现如果使用第二种操作,是选两个不同类型的位置。那么如果\(2x <= y\),直接每个地方用第一种操作就行;否则我们看这四个类型的位置可以选多少对来进行第二种操作,有一个经典问题:有\(n\)个数,每次选两个数减一,问最多操作几次。如果\(max >= sum / 2\)则可以配\(sum - max\)对,否则可以配\(sum - sum\%2\)对。根据这个结论来写就好,没有配对的用第一种操作就行。

点击查看代码
void solve() {
    int n, x, y;
    std::cin >> n >> x >> y;
    std::string a, b, c;
    std::cin >> a >> b >> c;
    i64 ans = 0, cnt[2][2]{};
	for (int i = 0; i < n; ++ i) {
		if (((a[i] - '0') ^ (b[i] - '0')) != c[i] - '0') {
			++ cnt[a[i] - '0'][b[i] - '0'];
		}
	}

    if (x * 2 <= y) {
    	ans = (i64)x * (cnt[0][1] + cnt[1][0] + cnt[0][0] + cnt[1][1]);
    } else {
    	std::vector<i64> a{cnt[0][0], cnt[0][1], cnt[1][0], cnt[1][1]};
    	std::sort(a.begin(), a.end());
    	if (a[3] >= a[0] + a[1] + a[2]) {
    		ans += (a[0] + a[1] + a[2]) * y;
    		ans += (a[3] - a[0] - a[1] - a[2]) * x;
    	} else {
    		i64 sum = a[0] + a[1] + a[2] + a[3];
    		ans += sum / 2 * y;
    		ans += (sum & 1ll) * x;
    	}
    }
    std::cout << ans << "\n";
}

D. 小L的字符串翻转

题意:给你一个01串,对于一个\(k\),把这个串\(k\)个一组分开,最后一段可以个数小于\(k\),然后你可以取反和重新排列每一组,最后把所有相邻的0和1都缩成一个0或1,价值就是最小的最终序列的长度。求\(1\)\(n\)的每个\(k\)的值的和。

如果一组中有1又有0,则必定使得长度加1,如果有\(m\)个连续的这样的组,则我们可以通过重新排列的方式使得相邻两组相邻的部分取一样的值,这样总共只会贡献\(m + 1\)的长度,如果是全1或全0,可以通过取反的方式和相邻的保持一致。所有对于每个\(k\)就是看有多少个有1又有0的分组。可以直接枚举每个\(k\),时间复杂度是一个调和级数。

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

    int ans = 0;
    for (int i = 1; i <= n; ++ i) {
    	int cnt = 1;
    	for (int j = 1; j <= n; j += i) {
    		int r = std::min(n, j + i - 1);
    		int one = sum[r] - sum[j - 1];
    		if (one != r - j + 1 && one != 0) {
    			++ cnt;
    		}
    	}

    	ans ^= cnt;
    }
    std::cout << ans << "\n";
}

E. 小L的井字棋

题意:一个\(3 \times 3\)的井字棋,已经下了几步,现在你先手,你有一个机会可以连下两步,求有没有必胜策略。

正解是分类讨论,我写的爆搜。
直接搜索枚举怎么下就行。

点击查看代码
void solve() {
    using A = std::array<std::string, 3>;
    A s;
    int sum = 0;
    for (int i = 0; i < 3; ++ i) {
    	std::cin >> s[i];
    	sum += std::count(s[i].begin(), s[i].end(), 'G');
    }

    auto check = [&](A s) -> bool {
    	for (int i = 0; i < 3; ++ i) {
    		if (s[i][0] == 'X' && s[i][1] == 'X' && s[i][2] == 'X') {
    			return true;
    		}

    		if (s[0][i] == 'X' && s[1][i] == 'X' && s[2][i] == 'X') {
    			return true;
    		}
    	}

    	if (s[0][0] == 'X' && s[1][1] == 'X' && s[2][2] == 'X') {
    		return true;
    	}

    	if (s[0][2] == 'X' && s[1][1] == 'X' && s[2][0] == 'X') {
    		return true;
    	}

    	return false;
    };

    auto dfs = [&](auto self, A s, int u, int flag, int cnt) -> bool {
    	if (cnt == 0) {
    		return check(s);
    	}

    	if (u == 0 && cnt >= 2 && flag) {
    		std::vector<std::pair<int, int>> a;
    		for (int i = 0; i < 3; ++ i) {
    			for (int j = 0; j < 3; ++ j) {
    				if (s[i][j] == 'G') {
    					a.push_back({i, j});
    				}
    			}
    		}

    		for (int i = 0; i + 1 < cnt; ++ i) {
    			auto & [x1, y1] = a[i];
    			for (int j = i + 1; j < cnt; ++ j) {
    				auto & [x2, y2] = a[j];
    				s[x1][y1] = s[x2][y2] = 'X';
    				if (self(self, s, u ^ 1, 0, cnt - 2)) {
    					return true;
    				}
    				s[x1][y1] = s[x2][y2] = 'G';
    			}
    		}
    	} else if (u == 0) {
    		for (int i = 0; i < 3; ++ i) {
    			for (int j = 0; j < 3; ++ j) {
    				if (s[i][j] == 'G') {
    					s[i][j] = 'X';
    					if (self(self, s, u ^ 1, flag, cnt - 1)) {
    						return true;
    					}
    					s[i][j] = 'G';
    				}
    			}
    		}
    	} else if (u == 1) {
    		bool flag = true;
    		for (int i = 0; i < 3; ++ i) {
    			for (int j = 0; j < 3; ++ j) {
    				if (s[i][j] == 'G') {
    					s[i][j] = 'L';
    					flag &= self(self, s, u ^ 1, flag, cnt - 1);
    					s[i][j] = 'G';
    				}
    			}
    		}

    		return flag;
    	}

    	return false;
    };

    if (dfs(dfs, s, 0, 1, sum)) {
    	std::cout << "Yes\n";
    } else {
    	std::cout << "No\n";
    }
}

F. 小L的抽卡

待补。


G. 小L的三元组

待补。


H. 小L的min-max问题

题意:给你一个数组,求每个子区间的\(max \times min\)之和。

赛时一直以为是dp,搞半天没搞出来。
考虑枚举一段分成的区间,那么只需要求出它到前面和后面有多少种组成\(k-1\)个区间的方案就行了。
设区间为\([i, j]\),那么左边有\(i - 1\)个数,右边有\(n - j\)个数,要分成\(k-1\)段,那么就是\(i - 1 + n - j\)个数在已经分成两段的情况下分成\(k-1\)段,得\(C(i - 1 + n - j - 2, k - 3)\)
特判\(i == 1\)\(j == n\)的情况,这两种情况并没有给其他数一开始分开,然后特判\(k==1\)的情况。

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

    std::vector C(n + 1, std::vector<Z>(n + 1));
    for (int i = 0; i <= n; ++ i) {
        for (int j = 0; j <= i; ++ j) {
            if (i == 0 || j == 0) {
                C[i][j] = 1;
            } else {
                C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
            }
        }
    }

    Z ans = 0;
    for (int i = 1; i <= n; ++ i) {
        int max = 0, min = 2e9;
        for (int j = i; j <= n; ++ j) {
            max = std::max(max, a[j]);
            min = std::min(min, a[j]);
            if (i == 1 && j == n) {
                if (k == 1) {
                    ans += (Z)max * min;
                }
            } else if (i == 1 && k >= 2) {
                ans += C[n - j - 1][k - 2] * max * min;
            } else if (j == n && k >= 2) {
                ans += C[i - 2][k - 2] * max * min;
            } else if (k >= 3) {
                ans += C[i + n - j - 3][k - 3] * max * min;
            }
        }
    }

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

I. 小L的数学题

题意:给你\(n, m\),你每次可以让\(n = n \times 2\)或者\(n = \lfloor \sqrt(n) \rfloor\),求\(n\)能否变成\(m\)

考虑最后一步是开平方,那么\(n' \in [m^2, (m+1)^2 - 1]\),同理如果倒数第二步是开平方,那么\(n'' \in [m^{2^{m^2}}, ((m+1)^2 - 1)^{(m+1)^2 - 1]}]\),发现其增加速度大于\(2\)的幂的增长速度,范围迟早包含某个\([2^i, 2^{i+1} - 1]\)。那么我们可以一直把\(n\)开平方到1,然后一直乘二到上面说的某个被覆盖的区间,然后一直开根号到\(m\)
注意特判\(n = 0\)或者\(m = 0\)

点击查看代码
void solve() {
	i64 n, m;
    std::cin >> n >> m;
   	if (n == 0) {
   		if (m == 0) {		
   			std::cout << "Yes\n";
   		} else {
   			std::cout << "No\n";
   		}
   		return;
   	}

   	if (m == 0) {
   		std::cout << "No\n";
   		return;
   	}

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

J. 小L的汽车行驶问题

题意:汽车每秒有个操作,会影响速度,问\(n\)秒共行驶多少米。

签到题。模拟即可。

点击查看代码
void solve() {
	int n;
	std::cin >> n;
    std::string s;
    std::cin >> s;
    i64 ans = 0, k = 0;
    for (auto & c : s) {
    	if (c == '0') {
    		k += 10;
    		ans += k;
     	} else if (c == '1') {
     		k = std::max(0ll, k - 5);
     		ans += k;
      	} else {
      		ans += std::max(0ll, k - 10);
      	}
    }

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

K. 小L的几何

题意:给你\(n\)个点,对于每个点\((x_i, y_i)\),求有多少点和他距离恰好是\(r_i\)

实际是求\(a^2 + b^2 = c^2\),求出每个\(r_i\)对于有多少对\(a, b\),则可以枚举四个方向统计答案。
求勾股数的证明官方视频题解说的很清楚,这里直接给结论,枚举两个奇数\(i, j\),那么\(a = i \times j, b = \frac{j \times j - i \times i}{2}, c = \frac{j \times j + i \times i}{2}\),然后枚举三个数同时翻倍就可以得到范围内的所有勾股数。
这题比较卡常,需要对点对hash一下。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::unordered_set<i64> s;
    std::vector<std::array<int, 3>> a(n);

    auto get = [&](int x, int y) -> i64 {
        return (i64)(x + 1000000) * 1000000 + y + 1000000;
    };

    for (int i = 0; i < n; ++ i) {
        int x, y, r;
        std::cin >> x >> y >> r;
        a[i] = {x, y, r};
        s.insert(get(x, y));
    }

    const int N = 4e5 + 5;
    std::vector<std::vector<std::pair<int, int>>> R(N);
    for (int i = 1; i < N; i += 2) {
        for (int j = i + 2; j < N; j += 2) {
            i64 a = (i64)i * j, b = ((i64)j * j - (i64)i * i) / 2, c = ((i64)i * i + (i64)j * j) / 2;
            if (a >= N || b >= N || c >= N) {
                break;
            }

            if (std::gcd(i, j) != 1) {
                continue;
            }

            for (int k = 1; k * c < N; ++ k) {
                R[k * c].push_back({a * k, b * k});
            }
        }
    }

    const int dx[] = {1, -1, -1, 1}, dy[] = {1, 1, -1, -1};
    i64 ans = 0;
    for (auto & [x, y, r] : a) {
        if (r >= N) {
            continue;
        }

        ans += s.count(get(x, y + r));
        ans += s.count(get(x, y - r));
        ans += s.count(get(x + r, y));
        ans += s.count(get(x - r, y));

        for (auto & [xd, yd] : R[r]) {
            for (int i = 0; i < 4; ++ i) {
                ans += s.count(get(x + dx[i] * xd, y + dy[i] * yd));
            }

            std::swap(xd, yd);

            for (int i = 0; i < 4; ++ i) {
                ans += s.count(get(x + dx[i] * xd, y + dy[i] * yd));
            }
        }
    }


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

L. 小L的构造

题意:求\([1, n]\)可以构造多少个三元组,满足两两个数里恰好有两对互质。

我的做法是,特判\(n < 6\)的情况。然后按照\(\{1, 2, 3\}, \{4, 5, 6\}...\)的方式构造,发现对于第\(i\)个三元组(\(i\)是奇数)只要拿它的第三个和\(i+1\)的第一个交换就可以满足这两个三元组。因为第奇数个三元组一定是三对互质,第偶数个三元组一定是两对互质,然后发现\(i\)的最后一个加三就是\(i+1\)的最后一个,它可以和\(i+1\)的第一个交换使得第\(i+1\)依然满足条件,然后\(i\)的第一个数和第二个数因为是相邻元素所以互质,而\(i+1\)的第一个元素等于\(i\)的第一个元素加三,同时\(i\)的第一个元素又不是3的倍数,所以它们也互质,然后\(i\)的第二个数和\(i+1\)的第一个数都是偶数,不互质,这样就满足了。然后特判三元组个数是奇数的情况,交换最后两个三元组的最后一个数就行,推理类似,因为倒数第二个三元组的第一个个倒数第三个的最后一个交换了,最后两个三元组这样交换是满足条件的。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::vector<std::array<int, 3> > a;

    if (n <= 3) {
    	std::cout << 0 << "\n";
    	return;
    }

    if (n < 6) {
    	std::cout << 1 << "\n";
    	std::cout << 2 << " " << 3 << " " << 4 << "\n";
    	return;
    }

    for (int i = 1; i + 2 <= n; i += 3) {
    	a.push_back({i, i + 1, i + 2});
    }

    int m = a.size();
    for (int i = 0; i + 1 < m; i += 2) {
        std::swap(a[i][2], a[i + 1][0]);
    }

    if (m & 1) {
    	std::swap(a[m - 1][2], a[m - 2][2]);
    }

    std::cout << m << "\n";
    for (auto & [x, y, z] : a) {
    	std::cout << x << " " << y << " " << z << "\n";
    }
}
posted @ 2025-02-08 21:58  maburb  阅读(125)  评论(0)    收藏  举报