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)×x+kx=n,得x=(nk)/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上两位的值。求ab=c的最小花费。

先把异或值不等于c的位置都存下来,可以分四类,根据这一位a的值和b的值分为00,01,10,11。然后发现如果使用第二种操作,是选两个不同类型的位置。那么如果2x<=y,直接每个地方用第一种操作就行;否则我们看这四个类型的位置可以选多少对来进行第二种操作,有一个经典问题:有n个数,每次选两个数减一,问最多操作几次。如果max>=sum/2则可以配summax对,否则可以配sumsum%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,价值就是最小的最终序列的长度。求1n的每个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×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×min之和。

赛时一直以为是dp,搞半天没搞出来。
考虑枚举一段分成的区间,那么只需要求出它到前面和后面有多少种组成k1个区间的方案就行了。
设区间为[i,j],那么左边有i1个数,右边有nj个数,要分成k1段,那么就是i1+nj个数在已经分成两段的情况下分成k1段,得C(i1+nj2,k3)
特判i==1j==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×2或者n=(n),求n能否变成m

考虑最后一步是开平方,那么n[m2,(m+1)21],同理如果倒数第二步是开平方,那么n[m2m2,((m+1)21)(m+1)21]],发现其增加速度大于2的幂的增长速度,范围迟早包含某个[2i,2i+11]。那么我们可以一直把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个点,对于每个点(xi,yi),求有多少点和他距离恰好是ri

实际是求a2+b2=c2,求出每个ri对于有多少对a,b,则可以枚举四个方向统计答案。
求勾股数的证明官方视频题解说的很清楚,这里直接给结论,枚举两个奇数i,j,那么a=i×j,b=j×ji×i2,c=j×j+i×i2,然后枚举三个数同时翻倍就可以得到范围内的所有勾股数。
这题比较卡常,需要对点对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 @   maburb  阅读(63)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示