蓝桥杯题型分类
语法基础
艺术与篮球(日期问题)
#include <bits/stdc++.h>
using namespace std;
map<char,int>myMap;
int basketball,calligraphy;
//日期是否合法模板
int months[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
bool check(int date)
{
int year=date/10000,month=date%10000/100,day=date%100;
if(!month||month>=13||!day)return false;
if(month!=2&&day>months[month])return false;
if(month==2)
{
int leap=(year%100!=0&&year%4==0)||year%400==0;
if(day>28+leap)return false;
}
return true;
}
int main()
{
// 插入数字与笔画数
myMap['0'] = 13;
myMap['1'] = 1;
myMap['2'] = 2;
myMap['3'] = 3;
myMap['4'] = 5;
myMap['5'] = 4;
myMap['6'] = 4;
myMap['7'] = 2;
myMap['8'] = 2;
myMap['9'] = 2;
// 遍历日期范围,从2000年1月1日到2024年4月13日
for(int i=20000101;i<=20240413;i++)
{
if(check(i))
{
string s=to_string(i);
int num=0;
for(auto j:s)
{
num+=myMap[j];
}
if(num>50)
{
basketball++;
}
}
}
cout<<basketball;
return 0;
}
时间显示(时间问题)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main()
{
ll x;
cin >> x;
// 计算小时、分钟、秒数
ll hh = (x / 3600000) % 24; // 小时数,取 24 小时制
ll mm = (x / 60000) % 60; // 分钟数,取 60 分钟
ll ss = (x / 1000) % 60; // 秒数,取 60 秒
// 输出时间格式
printf("%02lld:%02lld:%02lld", hh, mm, ss);
return 0;
}
跑步计划(日期问题)
#include <bits/stdc++.h>
using namespace std;
int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // 每个月的天数
// 检查数字是否包含1
bool check(int x) {
while (x) {
if (x % 10 == 1) {
return true; // 如果数字中包含1,返回true
}
x /= 10; // 去除最后一位
}
return false; // 如果没有1,返回false
}
int main() {
int ans = 0; // 总跑步的千米数
int week = 6; // 2023年1月1日是星期天,所以初始化为6(星期天)
// 遍历每个月
for (int i = 1; i <= 12; i++) {
// 遍历每个月的每一天
for (int j = 1; j <= months[i]; j++) {
week = (week % 7) + 1; // 更新星期几,确保在1到7之间循环(星期天为7)
// 如果日期、月份或星期几包含1,跑5千米
if (check(i) || check(j) || check(week)) {
ans += 5;
} else {
ans += 1; // 否则跑1千米
}
}
}
cout << ans; // 输出小蓝总共跑的千米数
return 0;
}
偶串(字符)
使用 Map 或者 unordered_map。
遍历字符串中的每个字符。对每个字符,检查它是否已经在你的数据结构中。如果是,增加它的计数。
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
string str;
cin >> str; // 输入字符串
unordered_map<char, int> char_count; // 用哈希表存储每个字符的出现次数
// 遍历字符串并统计每个字符的出现次数
for (char c : str) {
char_count[c]++;
}
// 检查是否每个字符出现次数为偶数
bool is_even = true;
for (auto& pair : char_count) {
if (pair.second % 2 != 0) { // 如果出现次数是奇数
is_even = false;
break;
}
}
// 输出结果
if (is_even) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
return 0;
}
创建一个大小为26的整数数组(假设为 cnt),用于存储每个小写字母的出现次数。数组的索引
0−25 分别对应字母 a-z。
遍历字符串的每一个字符(假设为 c):
将字符 c 转为其 ASCII 值。
通过计算 c - 'a' 来得到一个从 0
0 到 25 的索引,这个索引对应于字符 c。
使用这个索引来增加 cnt 数组中对应元素的值。
遍历结束后,cnt 数组中的每个元素就存储了对应字母在字符串中的出现次数。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
string s;
vector<int> cnt(26);
cin >> s;
for (auto c : s)
{
cnt[c - 'a']++;
}
for (int i = 0; i < 26; ++i)
{
if (cnt[i] % 2)
{
cout << "NO" << '\n';
return 0;
}
}
cout << "YES" << '\n';
return 0;
}
最长子序列(字符)
#include <iostream>
#include <string>
using namespace std;
int main() {
string s, t;
int num = 0;
cin >> s >> t;
for (char ch : t) {
// 查找当前字符ch在s中的位置
size_t pos = s.find(ch);
if (pos == string::npos) {
// 如果找不到字符,直接输出已找到的匹配数并结束程序
cout << num;
return 0;
} else {
// 如果找到了字符,更新字符串s并增加计数
num++;
s = s.substr(pos + 1); // 从pos+1开始截取s
}
}
// 输出最终匹配的字符数
cout << num;
return 0;
}
字母数(进制转换)
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int a[1000];
char ch[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
bool solve(int x)//十进制转换为16进制
{
string ans;
while(x)
{
if(ch[x%16]>='A')
{
ans+=ch[x%16];
x/=16;
}
else
{
return false;
}
}
reverse(ans.begin(),ans.end());
return true;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int i=2022;
while(true)
{
i++;
if( solve(i))
{
cout<<i;
return 0;
}
}
return 0;
}
6个0(进制转换)
#include <bits/stdc++.h>
using namespace std;
bool check(int x)
{
// 检查最低的 5 位是否都为 0
for(int i = 0; i < 5; i++)
{
if(((x >> i) & 1) != 0) // 如果第 i 位不为 0,返回 false
{
return false;
}
}
return true;
}
int main()
{
int x = 2022; // 从 2022 开始查找
while(true)
{
x++; // 从 2023 开始检查
if(check(x)) // 如果 x 的最低 5 位全为 0
{
cout << x;
return 0;
}
}
return 0;
}
优秀的拆分(位运算)
- 输入输出样例
示例 1
输入
6
输出
4 2
示例 2
输入
7
输出
-1
7的二进制数为(0111),6的二进制数为(0110),可以发现7的二进制位的最低位(第0位)为1,值为
2
0
2^0
20 ,所以只要最低位为1,就不是优秀的拆分。我的从最高位开始遍历,只要第i位为1,我们就输出 1<<i ,即为
2
i
2^i
2i
#include <bits/stdc++.h>
using namespace std;
int main() {
int num;
cin >> num;
// 如果最低位为 1,输出 -1
if (((num >> 0) & 1) == 1) {
cout << -1 << endl;
return 0;
}
// 从最高位开始遍历,检查每一位
for (int i = 30; i >= 0; i--) {
// 如果当前位为 1,输出 2^i
if (((num >> i) & 1) == 1) {
cout << (1 << i) << " ";
}
}
return 0;
}
异或数列(位运算)
示例 1
输入
4
1 1
1 0
2 2 1
7 992438 1006399 781139 985280 4729 872779 563580
输出
1
0
1
1
解题思路
我们设在游戏结束后 Alice 的数变为 c
,Bob 的数变为 d
。
我们先来解决平局的情况:
根据异或性质可得:若 c = d
,则 c ⊕ d = 0
。
而 c ⊕ d = X1 ⊕ X2 ⊕ ⋯ ⊕ Xn
,所以要使 c = d
,当且仅当 X1 ⊕ X2 ⊕ ⋯ ⊕ Xn = 0
。
接下来定输赢:
我们将 c, d
转换成二进制数。对于二进制数的比较,我们是从高位往低位开始的。所以要使自己的数最大,就需要从高位开始。
设当前枚举到二进制的第 i
位。设 X1, X2, X3, ..., Xn
中一共有 cnt1
个数在该位的值为 1
,cnt2
个数在该位的值为 0
。(cnt1 + cnt2 = n
)
结论一:
如果 cnt1
为偶数,则 Alice 和 Bob 无法在该位分出胜负。
证明方法和上述平局情况相同。 或者也可以这么想:cnt1
为偶数,那么 Alice 和 Bob 要么都从这 cnt1
个数中分到偶数个,要么 Alice 和 Bob 都在这 cnt1
个数中分到奇数个;所以无论怎么分配,c, d
在该位的异或值都必然相同。
反之当 cnt1
为奇数时,必然能决出胜负。证明方法和上述类似,就不再给出。
那么 cnt1
为奇数时如何判断谁输谁赢呢?
我们先定义,对于当前第 i
位,如果能让自己的数值从 0 → 1
,或者能让对手的数值从 1 → 0
,则自己的胜率 +1
;如果让自己的数值从 1 → 0
,或者让对手的数值从 0 → 1
,则自己的胜率 -1
;如果既不改变自己的数值,也不改变对手的数值,则自己的胜率不变。显然,游戏结束时,胜率越高的一方获胜。
结论二:
当 cnt1
为奇数,cnt2 = 0
时,先手必胜。
证明:
模拟一下可以发现先手后手走的每一步都必然是让自己胜率增加的一步。由于 cnt1
为奇数,所以先手可以比后手多走一步,所以先手的胜率必然会比后手高。
那么什么情况下必然会使自己的胜率减少呢?即当自己的数值为 1
,且对手的数值为 0
,且公共数列中只有 1
可以选取时。
结论三:
谁的胜率率先减少,则谁必败。
证明: 由于一方胜率减少了,所以可得公共数列中只有 1
可以选取,没有 0
可以选取。
设胜率率先减少的 Alice
,那么此时 Alice
和 Bob
的数值只有两种可能:
Alice
的数值为0
,Bob
的数值为0
;Alice
的数值为1
,Bob
的数值为1
。
由于 Alice
和 Bob
的数值相同,所以公共序列中使用的 1
的个数必然为偶数,剩余的 1
的个数必然为奇数。且此时是 Bob
先手,根据结论二,Bob
必胜,Alice
必败,证明完毕。
那么谁的胜率会先减少的呢?
结论四:
当 cnt1
、cnt2
为奇数时且 cnt1 > 1
时,先手的胜率会率先减少。
证明: 当 cnt2
为奇数时,先手第一步只能选取 0
或是 1
:
- 若先手先取
1
则后手取0
。此时先手的数值为1
,后手的数值为0
。为了不让自己的胜率降低,先手只能取0
,而后手也接着取0
。由于0
个为奇数,所以先手将率先无法取0
,只能取1
,使得自己的胜率降低。 - 若先手取
0
则后手也取0
。此时场面还是1
的个数为奇数,0
的个数为奇数的情况。若先手率先取了1
,则就回到了上述的情况,先手必败。所以先手只能不断取0
,而后手也跟着不断取0
。最后先手取完0
,将剩余奇数个1
,回到了结论三的情况。由于此时到了后手的轮次,所以先手必败。
结论五:
当 cnt1 = 1
时,先手必胜。
证明略。
根据上述五个结论,模拟一遍即可。
复杂度为 O(22 ∑ i=1 Tni)
。
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int a[N];
signed main() {
int T = 1;
cin >> T;
while(T--) {
int n, sum = 0, ans = 0;
cin >> n;
// 读取输入并计算异或和
for(int i = 1; i <= n; i++) {
cin >> a[i];
sum ^= a[i];
}
// 结论1:如果异或和为 0,则平局
if (!sum) {
cout << 0 << '\n';
continue;
}
// 对每一位进行分析
for (int j = 20; j >= 0; j--) {
int one = 0, zero = 0;
// 统计当前位上的1和0的数量
for (int i = 1; i <= n; i++) {
if (a[i] >> j & 1) one++;
else zero++;
}
// 结论2 如果当前位有奇数个1,则确定胜负
if (one & 1) {
if (zero % 2 && one != 1) ans = -1; //结论4 不满足条件,Bob 获胜
else ans = 1; // 满足条件,Alice 获胜
break;
}
}
cout << ans << '\n'; // 输出结果
}
return 0;
}
幸运数字的个数(预计算)
样例输入
6
1 10
1 16
1 17
10 100
100 1000
1000 10000
样例输出
10
15
16
11
13
14
说明
对于所有评测数据:
1 ≤ T ≤ 1 0 5 , 1 ≤ l i ≤ r i ≤ 1 0 12 。 1≤T≤10^5,1≤l_i ≤r_i≤10^{12} 。 1≤T≤105,1≤li≤ri≤1012。
要用到的思想是先“离线”预计算所有可能的幸运数字,再用二分查找快速计算每个查询区间内的幸运数字数量。具体做法如下:
先枚举所有“十六进制中由同一字符重复”的数字,排除超过 10^12 的值,并将这些数字存储到一个数组并排序;
对每次给定的范围 [l, r],使用二分查找定位区间上下界,从而快速统计落在该区间内的幸运数字个数。
#include <bits/stdc++.h>
using namespace std;
static const long long MAX_VAL = 1000000000000LL; // 1e12
// 预先生成所有在 [1, 1e12] 范围内 "十六进制由同一数字重复" 的幸运数字
// 注意:digit 取值范围是 [0..15],长度取值范围适当即可(1~16足够覆盖1e12)
vector<long long> generateLuckyNumbers() {
vector<long long> luckyNums;
// 十六进制最大可用字符:0~f (共16个)
for (int digit = 0; digit < 16; ++digit) {
for (int length = 1; length <= 16; ++length) {
// 构建长度为 length 的重复字符
// 例如若 digit = 12 (十六进制 c),length = 4,则是"cccc"
// 然后转为十进制,判断是否 <= 1e12
// digit 转成对应的16进制字符
char hexDigit;
if (digit < 10) {
hexDigit = char(digit + '0');
} else {
hexDigit = char(digit - 10 + 'a');
}
// 构建重复串
string hexStr(length, hexDigit);
// 转成十进制
// stoll(hexStr, nullptr, 16) 有可能超范围,用更安全方式
// 这里用 64位整型计算
long long num = 0;
for (char c : hexStr) {
// digitVal 可以用 c - '0' 或 c - 'a' + 10
// 但我们已经知道是同一个字符
int val;
if (isdigit(c))
val = c - '0';
else
val = c - 'a' + 10;
num = num * 16 + val;
// 若已经超过范围就中断
if (num > MAX_VAL) break;
}
if (num > 0 && num <= MAX_VAL) {
luckyNums.push_back(num);
}
}
}
// 去重并排序
sort(luckyNums.begin(), luckyNums.end());
luckyNums.erase(unique(luckyNums.begin(), luckyNums.end()), luckyNums.end());
return luckyNums;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
// 预先生成所有可能的幸运数字
static vector<long long> luckyNumbers = generateLuckyNumbers();
int T;
cin >> T;
while (T--) {
long long l, r;
cin >> l >> r;
// 在 luckyNumbers 中,用二分查找统计区间 [l, r] 内的元素个数
auto leftIt = lower_bound(luckyNumbers.begin(), luckyNumbers.end(), l);
auto rightIt = upper_bound(luckyNumbers.begin(), luckyNumbers.end(), r);
cout << (rightIt - leftIt) << "\n";
}
return 0;
}
填空
握手问题
对于第一个人来说 除了自己以外要跟其他49人握手 所以第一个是49 ,对于第二个人来说 第一个人主动跟我握手了 有一次不算 所以第二个是48.。 以此类推 第43个人就是7 到了最后七个人呢 因为互相都没有握手 并且7个人都被前面的人握过手了 所以都是0
#include <iostream>
using namespace std;
int main(){
int sum=0;
for(int i=7;i<=49;i++) sum+=i;
cout<<sum;
return 0;
}
报数问题
第1-10个: 20 24 40 48 60 72 80 96 100 120
第11-20个:140 144 160 168 180 192 200 216 220 240
第21-30个:260 264 280 288 300 312 320 336 340 360
第31-40个:380 384 400 408 420 432 440 456 460 480
思路一:发现第10个数,第20个数,第30个数,第40个数......(每十个数为一轮)等等都是120的倍数,
既然题目要求第202420242024个数,那我们不妨先求第202420242020个数,然后再往后再多求4个数就行。
也就是202420242020/10*120=202429042904240,找它之后的四个能被20或24整除的数,也就是
2429042904288
思路二:通过观察发现,第奇数位个数是20的倍数,第偶数位个数是24的倍数。所以第202420242024个数
就是24的倍数,那我们直接除以2(判断是这个数是第几个24的倍数),然后再乘24就行。
也就是202420242024÷2×24=2429042904288
杂题
游戏专家(零和博弈)
输入格式
一行一个字符串
s(1≤∣s∣≤1000)由小写英文字母组成。
样例输入
bazabyakslfd
样例输出
zbybzazazaza
分治思想(交替处理策略)先行者和后行者对应偶数和奇数
我们轮流让小蓝和小桥修改字符串,小蓝尽量将字符变成字典序最大的字母 z,小桥尽量将字符变成字典序最小的字母 a。
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin >> s;
int n = s.size();
// 轮流修改字符串
for (int i = 0; i < n; i++)
{
if (i % 2 == 0) // 小蓝的回合,尽量使字典序最大
{
if (s[i] != 'z') // 如果当前字符不是'z',则将其改为'z'
{
s[i] = 'z';
}
}
else // 小桥的回合,尽量使字典序最小
{
if (s[i] != 'a') // 如果当前字符不是'a',则将其改为'a'
{
s[i] = 'a';
}
}
}
cout << s; // 输出最终字符串
return 0;
}
大衣的异或回文对(回文判断)
样例输入1
4
13 27 12 26
样例输出1
8
样例输入2
3
2 2 2
样例输出2
6
使用字符判断回文
// 判断整数 x 是否是回文
bool isPalindrome(int x) {
string s = to_string(x);
string rev_s(s.rbegin(), s.rend());
return s == rev_s;
}
使用数字判断回文
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 2e4 + 10;
ll a[N];
ll ans;
// 判断是否是回文数
bool hw(ll n) {
ll sum = 0;
ll k = n;
while (n != 0) {
sum = (sum * 10) + (n % 10); // 反转数字
n /= 10;
}
return sum == k; // 如果反转的数字等于原数字,则为回文数
}
int main() {
ll n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
// 遍历所有对 (i, j) 计算异或并判断是否是回文数
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
if (hw(a[i] ^ a[j])) {
ans++;
}
}
}
cout << ans << endl;
return 0;
}
寻找至宝的奥秘(数学)
最大公因数的基本概念:
最大公因数(GCD)是指两个数的最大共有因子。比如,gcd(12, 15) = 3
,因为 12
和 15
都能被 3
整除,而没有比 3
更大的共同因子。
思路分析:
-
最大公因数的性质:
- 假设我们有两个正整数
a
和b
,如果它们的 GCD 很大,那么这两个数的因子也应该尽量重合。 - 如果我们选取
a = n
和b = n / 2
,那么它们的 GCD 通常会比较大。具体来说,gcd(n, n/2)
总是n/2
(这是因为n/2
是n
的因子,并且它们共享n/2
作为共同因子)。
- 假设我们有两个正整数
-
为什么选择
n
和n / 2
:-
选择
n
和n / 2
作为候选:- 如果我们选择两个数
a = n
和b = n / 2
,这两个数之间的最大公因数是n / 2
。这是因为:n
是n / 2
的倍数,n
和n / 2
的最大公因数就是n / 2
。
- 例如,当
n = 10
时,n = 10
和n / 2 = 5
,这两个数的 GCD 是 5。
- 如果我们选择两个数
-
为什么
n / 2
会是最大值:- 当我们选择两个数时,我们希望它们的最大公因数尽可能大。
n / 2
是n
最大的因子之一,所以选择n
和n / 2
总是能得到最大的 GCD。
- 当我们选择两个数时,我们希望它们的最大公因数尽可能大。
-
-
其他可能的组合:
- 如果选择
a = n
和b = n - 1
,它们的最大公因数一般会较小,因为相邻的整数的 GCD 总是1
。 - 同理,其他的一些数对,如
a = n
和b = n - 2
等,都会比n / 2
和n
的 GCD 小。
- 如果选择
例子:
-
例子 1:
输入:n = 10
- 我们选择
a = 10
和b = 10 / 2 = 5
,计算gcd(10, 5)
,结果是5
。 - 因为
10
和5
的最大公因数是5
,这是最大的 GCD。
- 我们选择
-
例子 2:
输入:n = 12
- 我们选择
a = 12
和b = 12 / 2 = 6
,计算gcd(12, 6)
,结果是6
。 - 因为
12
和6
的最大公因数是6
,这是最大的 GCD。
- 我们选择
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
// 输出 n / 2 即可得到最大公因数
cout << n / 2 << endl;
return 0;
}
小蓝的战斗计划
- 优先尝试消灭需要 2 个单位时间的怪物:遍历排序后的所有时间段 b[i],如果 b[i] ≥ 2,就尽可能使用该时间段来消灭“需要 2 个单位时间”的怪物(min(cnt2, b[i]/2))。在此操作中,消灭多少头怪物,就从 cnt2 和 b[i] 各自对应的“数量”中减去相应数值。
- 然后尝试消灭需要 1 个单位时间的怪物:再次遍历同一个排序后的时间段数组,如果 b[i] ≥ 1,就用这个时间段去消灭尽量多的 cnt1 怪物(min(cnt1, b[i]))。
- 最后检查 cnt1 和 cnt2 是否都被消灭(即 cnt1 == 0 && cnt2 == 0)。若全部消灭则输出 “Y”,否则输出 “N”。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, m;
int cnt1 = 0, cnt2 = 0;
int a[N], b[N];
int main() {
cin >> n >> m;
// 输入怪物的战斗时间,并统计需要时间1和时间2的怪物数量
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (x == 1) {
cnt1++;
} else {
cnt2++;
}
}
// 输入时间段的长度
for (int i = 1; i <= m; i++) {
cin >> b[i];
}
// 对时间段进行降序排序
sort(b + 1, b + 1 + m, [](int u, int v) { return u > v; });
// 优先使用时间段消灭需要时间2的怪物
//由于已通过 b[i]/2 把每只“2单位时长”怪物向下折算成占 1 个“怪物位置”的逻辑,
//因此只需要在放入 x 只后执行 b[i] -= x(而不是 b[i] -= 2*x)
for (int i = 1; i <= m && cnt2 > 0; i++) {
if (b[i] >= 2) { // 如果时间段长度>=2,可以消灭一个需要时间2的怪物
int x = min(cnt2, b[i] / 2); // 计算能消灭多少个怪物
cnt2 -= x; // 更新需要消灭时间2的怪物数量
}
}
// 使用剩余的时间段消灭需要时间1的怪物
for (int i = 1; i <= m && cnt1 > 0; i++) {
if (b[i] >= 1) { // 如果时间段长度>=1,可以消灭一个需要时间1的怪物
int x = min(cnt1, b[i]); // 计算能消灭多少个怪物
cnt1 -= x; // 更新需要消灭时间1的怪物数量
}
}
// 判断是否所有怪物都被消灭
if (cnt1 == 0 && cnt2 == 0) {
cout << "Y" << endl;
} else {
cout << "N" << endl;
}
return 0;
}
公司名称(字符串)
样例输入
5
7
Lqaaoin
7
Lanqiao
8
Lanqiaoo
2
ac
7
niLaqqa
样例输出
YES
YES
NO
NO
NO
#include <bits/stdc++.h>
using namespace std;
int main() {
string name = "Lanqiao";
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
string s;
cin >> s;
if (name.length() == s.length()) {
for (char j : name) {
int pos = s.find(j);
if (pos != string::npos) {
s.erase(pos, 1); // 从pos开始删除一个字符
}
}
if (s.empty())
cout << "YES" << endl;
else
cout << "NO" << endl;
} else {
cout << "NO" << endl;
}
}
return 0;
}
航班时间(字符串读取+方程式)
输出描述
对于每一组数据输出一行一个时间 hh:mm:ss,表示飞行时间为 hh 小时 mm 分 ss 秒。
注意,当时间为一位数时,要补齐前导零。如三小时四分五秒应写 03:04:05。
输入输出样例
示例
输入
3
17:48:19 21:57:24
11:05:18 15:14:23
17:21:07 00:31:46 (+1)
23:02:41 16:13:20 (+1)
10:19:19 20:41:24
22:19:04 16:41:09 (+1)
输出
04:09:05
12:10:39
14:22:05
#include<bits/stdc++.h>
using namespace std;
int getTime(void)
{
int h1,m1,s1,h2,m2,s2,d=0;
scanf("%d:%d:%d %d:%d:%d (+%d)",&h1,&m1,&s1,&h2,&m2,&s2,&d);
int time=d*24*3600+h2*3600+m2*60+s2-(h1*3600+m1*60+s1);
return time;
}
int main()
{
int t;
scanf("%d",&t);
for(int i = 0; i < t; i++)
{
int time1=getTime();
int time2=getTime();
int t=(time1+time2)/2;
printf("%02d:%02d:%02d\n", t/3600, t/60%60, t%60);
}
return 0;
}
也可以使用sscanf,关于具体用法,可见传送门
#include <bits/stdc++.h>
using namespace std;
// get_time 函数用于计算时间差(单位为秒)
int get_time()
{
string line;
getline(cin, line); // 读一行时间字符串
// 如果时间字符串没有以 ")" 结尾,则添加 "(+0)"
if (line.back() != ')')
line += " (+0)";
// 定义起飞时间和到达时间的各个组件
int h1, m1, s1, h2, m2, s2, day;
// 解析时间字符串,提取小时、分钟、秒和天数
sscanf(line.c_str(), "%d:%d:%d %d:%d:%d (+%d)", &h1, &m1, &s1, &h2, &m2, &s2, &day);
// 将时间转为秒数,起飞时间和到达时间
int S = h1 * 3600 + m1 * 60 + s1; // 起飞时间:转为秒
int E = h2 * 3600 + m2 * 60 + s2; // 到达时间:转为秒
// 返回时间差,考虑到天数影响
return E - S + day * 24 * 3600;
}
int main()
{
string line;
getline(cin, line); // 读取第一行
int n;
// 解析第一行,获取组数 n
sscanf(line.c_str(), "%d", &n);
// 循环处理每组数据
while (n--)
{
// 计算两次时间差的平均值
int ans = (get_time() + get_time()) / 2;
// 输出结果,格式化为时:分:秒
printf("%02d:%02d:%02d\n", ans / 3600, ans / 60 % 60, ans % 60);
}
}
蓝桥村的神秘信件(字符串)
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
int N;
string S;
cin >> N;
cin >> S;
// 用哈希表来统计每个字符的频率
unordered_map<char, int> freq;
// 统计字符频率
for (int i = 0; i < N; i++) {
freq[S[i]]++;
}
// 查找第一个只出现一次的字符
for (int i = 0; i < N; i++) {
if (freq[S[i]] == 1) {
cout << S[i] << endl;
return 0; // 找到第一个只出现一次的字符后,直接返回
}
}
// 如果没有找到,只出现一次的字符
cout << -1 << endl;
return 0;
}
打开石门
#include <bits/stdc++.h>
using namespace std;
int main(){
string s;
cin >> s; // 读取输入字符串
int a = 0, b = 0; // 初始化两个计数器:a用于统计LL对数,b用于统计QQ对数
// 遍历字符串,检查相邻的字符对
for (int i = 1; i < s.size(); i++) {
if (s[i-1] == 'L' && s[i] == 'L') a++; // 如果是LL相邻,a加1
if (s[i-1] == 'Q' && s[i] == 'Q') b++; // 如果是QQ相邻,b加1
}
// 输出最终能缩短到的最小长度
cout << s.size() - max(a, b); // 减去最大合并次数,得到最小长度
return 0;
}
诺伊的神秘密码(字符串切割)
#include <bits/stdc++.h>
using namespace std;
// 左旋操作:将第一个字符移动到末尾
string left(string s)
{
return s.substr(1, s.size() - 1) + s[0];
}
// 右旋操作:将最后一个字符移动到开头
string right(string s)
{
return s[s.size() - 1] + s.substr(0, s.size() - 1);
}
int main()
{
string s;
cin >> s; // 输入字符串 S
// 分别计算左旋和右旋后的结果
string lefts = left(s);
string rights = right(s);
// 判断左旋和右旋后的结果是否相同
if (lefts == rights)
{
cout << "YES"; // 如果相同,则输出 YES
}
else
{
cout << "NO"; // 如果不同,则输出 NO
}
return 0;
}
超级的大串 (字符组合)
假设我们正在处理字符串 str = "abz"
, 且我们正在处理第 1 个字符 'b'
。
- 如果我们选择一个字符
'c'
或更大的字符来替代'b'
,那么在'b'
后面的位置(即位置 2)可以选择任意的字符,直到字符串的结尾。假设右边的部分是'z'
,可以替换成'a'
、'b'
、'c'
…'z'
,所以右侧的字符部分可以有 26 种可能。
为什么要用 26 的幂:
- 字符串的每个位置有 26 个可能的字符(从
'a'
到'z'
)。 - 对于当前字符,如果我们选择了一个字典序大于当前字符的字符,那么剩下的字符都可以是任意的(即它们有 26 种可能)。
例如:
- 如果我们选择
'c'
代替'b'
,那么'z'
后面的字符可以是任何字母,因此可能的组合数就是26
。 - 如果字符串中还有更多字符,那么我们可以继续这样选择。
对应的代码:
for (int j = 0; j < str.size() - i - 1; j++)
res = res * 26 % mod;
-
str.size() - i - 1
:这表示当前字符右边的字符数,i
是当前字符的索引。所以我们知道在str[i]
后面有str.size() - i - 1
个字符需要考虑。 -
res = res * 26 % mod
:表示对于每一个后续位置,都会有 26 种可能的选择。所以我们用 26 来乘上当前的结果res
,并且对mod
取模,确保结果不会溢出。
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
const int MOD = 998244353;
int n;
string s;
ll ans = 0; // 结果变量需要初始化
int main()
{
cin >> n >> s;
// 从字符串的每个字符开始遍历
for (int i = 0; i < s.size(); i++) {
ll res = 1;
// 计算当前字符 's[i]' 在字母表中的位置
int t = s[i] - 'a' + 1; // 字母 'a' 的位置是 1,'b' 是 2,以此类推
if (t == 26) {
continue; // 如果当前字符是 'z',跳过,因为没有比 'z' 更大的字符
} else {
// 对于当前位置后面的字符,每个字符有 26 种可能
for (int j = 0; j < s.size() - i - 1; j++) {
res = res * 26 % MOD; // 计算后续字符的所有组合
}
// 计算比当前字符大的字符数
res = res * (26 - t) % MOD;
// 累加结果
ans = (ans + res) % MOD;
}
}
// 输出结果并对 998244353 取模
cout << ans << endl;
return 0;
}
食堂活动 (哈希字符)
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
// 用数组或 map 存储每种菜的价格
// 字符是小写字母,所以我们可以用 char -> int 下标
vector<long long> price(26, 0);
for(int i = 0; i < n; i++){
char c;
long long p;
cin >> c >> p;
price[c - 'a'] = p;
}
// 读取点餐字符串
string order;
cin >> order;
// 统计每种菜的点餐数量
vector<long long> count(26, 0);
for(char c : order){
count[c - 'a']++;
}
// 计算总价
// 活动:每点两份同种菜,就送一份同种菜,即3份只需付2份钱
long long ans = 0;
for(int i = 0; i < 26; i++){
if(count[i] > 0 && price[i] > 0){
long long x = count[i];
long long fullSets = x / 3; // 每3份只付2份钱
long long remainder = x % 3;
long long costForDish = price[i] * (2LL * fullSets + remainder);
ans += costForDish;
}
}
cout << ans << "\n";
return 0;
}
特殊日期
#include <iostream>
using namespace std;
long long months[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool check(long long year)
{
return (year%4==0&&year%100!=0)||year%400==0;
}
long long ans;
int main()
{
for(int i=2000;i<2000000;i++)
{
// 判断是否为闰年,并更新2月天数
if(check(i))
{
months[2]=29;
}
else
{
months[2]=28;
}
for(int j=1;j<=12;j++)
{
if(i%j==0) // 如果年份是该月份的倍数
{
for(int k=1;k<=months[j];k++)// 遍历该月的每一天
{
if(i%k==0) // 如果年份是该天数的倍数
{
ans++;
}
}
}
}
}
ans++;// 2000000.1.1 不要忘记这个日期
cout<<ans;
return 0;
}
高斯日记(日期差值)
#include <iostream>
using namespace std;
int months[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// 判断是否为闰年
bool check(int year) {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
int main() {
int d = 8113 - 5343 - 16; // 天数差
int year = 1792, month, day;
// 逐年递减,找到目标日期所在的年份
while (d > 365) {
d -= (365 + check(year)); // 减去每年天数,闰年则为366天
year++;
}
// 判断目标年份是否是闰年,更新2月天数
if (check(year)) {
months[2] = 29; // 闰年2月29天
} else {
months[2] = 28; // 非闰年2月28天
}
// 逐月递减,找到目标月份和日期
for (month = 1; d > months[month]; month++) {
d -= months[month]; // 减去当前月的天数
}
day = d; // 剩余的天数就是目标日期
// 输出结果,确保格式为 yyyy-mm-dd
printf("%04d-%02d-%02d", year, month, day);
return 0;
}
跑步锻炼(日期问题)
我们用 (sum + 6) % 7 来计算当前日期是星期几。为什么要加6呢?因为我们假设 2000年1月1日 是星期六,所以要调整到星期六作为起点。
#include <iostream>
using namespace std;
int months[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
// check函数用于验证日期是否合法
bool check(int date)
{
int year = date / 10000; // 提取年份
int month = date % 10000 / 100; // 提取月份
int day = date % 100; // 提取日期
if (!month || month > 12 || !day) return false;
if (month != 2 && day > months[month]) return false;
if (month == 2)
{
int leap = year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
if (day > 28 + leap) return false;
}
/
return true;
}
int main()
{
int ans = 0; // 记录符合条件的日期数
int sum = 0; // 记录当前日期的星期几(从2000年1月1日开始)
// 从2000年1月1日到2020年10月1日逐日检查
for (int i = 20000101; i <= 20201001; i++)
{
// 检查当前日期是否有效
if (check(i))
{
ans++; // 如果是有效日期,增加答案计数
int month = i % 10000 / 100; // 提取当前日期的月份
int day = i % 100; // 提取当前日期的日期
// 如果是1号或者当前是星期一,增加答案计数
if (day == 1 || (sum + 6) % 7 == 1)
{
ans++; // 1号或者星期一时,计数加1
}
sum++; // 增加天数,更新星期几
}
}
// 输出符合条件的日期数
cout << ans;
return 0;
}
回文日期
#include <iostream>
using namespace std;
int month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
// check函数用于判断日期是否合法
bool check(int n)
{
// 分解年份、月份和日期
int y = n / 10000;
int m = n % 10000 / 100;
int d = n % 100;
if (!m || !d || m >= 13) return false;
if (m != 2 && d > month[m]) return false;
if (m == 2)
{
int leap = y % 400 == 0 || (y % 4 == 0 && y % 100 != 0);
if (d > 28 + leap) return false;
}
return true;
}
// check_abab函数用于检查日期是否符合"abab"格式
bool check_abab(int n)
{
int a = n / 10000000; // 提取第一个数字
int b = n / 1000000 % 10; // 提取第二个数字
int c = n / 100000 % 10; // 提取第三个数字
int d = n / 10000 % 10; // 提取第四个数字
// 检查是否符合"abab"格式且a与b不相等
if (a == c && b == d && a != b) return true;
return false;
}
int main()
{
int n;
cin >> n; // 输入日期(格式为YYYYMMDD)
int flag = 1; // 用于标记是否已找到满足条件的日期
// 从输入日期的年份开始逐年遍历
for (int i = n / 10000; i <= 10000; i++)
{
int date = i, x = i;
// 构造出该年份的“abab”格式日期
for (int j = 0; j < 4; j++)
{
date = date * 10 + x % 10; // 将当前年份的最后一个数字逐个加到日期中
x /= 10; // 去除最后一位数字
}
// 如果构造的日期有效,且大于输入日期,并且flag为1,则输出该日期
if (check(date) && flag && date > n)
{
cout << date << endl;
flag = 0; // 找到第一个符合条件的日期后,设flag为0,防止再次输出
}
// 如果构造的日期有效,并且符合"abab"格式,且大于输入日期,则输出并结束程序
if (check(date) && check_abab(date) && date > n)
{
cout << date;
return 0;
}
}
return 0;
}
特殊时间(枚举日期)
#include <bits/stdc++.h>
using namespace std;
// 定义每个月所包含的天数(下标 0 未用)
int day_per_month[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/**
* @brief 检查传入的四位数 D(形如 MMDD)是否是一个合法日期
* @param D 整数表示月日,如 0229、1231
* @return 若是有效的月/日则返回 true,否则返回 false
*/
bool check_D(int D){
// 月份放在高两位
int month = D / 100;
// 天数放在低两位
int day = D % 100;
// 月份合法区间为 [1, 12]
if(month < 1 || month > 12) return false;
// 日数合法区间根据当月天数判定
if(day < 1 || day > day_per_month[month]) return false;
return true;
}
/**
* @brief 检查传入的四位数 H(形如 HHMM)是否是一个合法时间
* @param H 整数表示小时分钟,如 0020、2359
* @return 若是有效的小时/分钟则返回 true,否则返回 false
*/
bool check_H(int H){
// 小时放在高两位
int h = H / 100;
// 分钟放在低两位
int m = H % 100;
// 小时合法区间为 [0, 23]
if(h < 0 || h > 23) return false;
// 分钟合法区间为 [0, 59]
if(m < 0 || m > 59) return false;
return true;
}
/**
* @brief 主函数:通过枚举方式,统计由两个不同数字 (a, b) 组合成
* “3个相同 + 1个不同” 的4位数(用于表示年份、月日、时分)的有效组合
*/
int main(){
int ans = 0; // 统计符合要求的时间个数
// 外层循环枚举第一个数字 a
for(int a = 0; a <= 9; a++){
// 内层循环枚举第二个数字 b,确保 a 与 b 不同
for(int b = 0; b <= 9; b++){
if(a != b){
// 年份写成 4 位时全部使用 a,因此年份可以看作 aaaa
// 程序中将其视作 "有 4 种情况"(N_Y=4),只是为了乘法计算便捷
int N_Y = 4; // 在本题理解为“年份 aaaa”时的简单做法
int N_D = 0; // 用来统计"3a + 1b" 所构造的四位数在日期上的合法个数
int N_H = 0; // 用来统计"3a + 1b" 所构造的四位数在时刻上的合法个数
// 先把数组 A 初始化为 [a, a, a, a]
int A[4] = {a, a, a, a};
// 通过把 b 放在 4 个位置之一,枚举出 4 种排列:aaab, aaba, abaa, baaa
for(int i = 0; i < 4; i++){
A[i] = b; // 将 b 放在第 i 个位置
int number = 0;
// 将 A[0..3] 拼接成一个四位数
for(int j = 0; j < 4; j++){
number = number * 10 + A[j];
}
// 检查该四位数是否能表示一个合法的 日期(月日)
N_D += check_D(number);
// 检查该四位数是否能表示一个合法的 时刻(小时分钟)
N_H += check_H(number);
A[i] = a; // 恢复现场,继续处理下一个位置
}
// ans += 年份的 4 种可能 * 合法日期数 * 合法时刻数
ans += N_Y * N_D * N_H;
}
}
}
// 输出结果
cout << ans << endl;
return 0;
}
高精度
进制转换
进制转换的实现与取余和取模相关:以十进制数173转换为二进制10101101为例
我们可以看到转换进制的具体流程就是对输入的数对2取模得到1,再对其对2整除得到下一个待处理数,直到待处理的数变成0,便将刚才得到的余数逆序输出。
那么观察一下这里的2,其实就是二进制的基数2
看完这张图,我们的脑海里应该有了大体结构,就是我们的程序输入基数,输入待处理的数字173,然后通过一个循环结构得到余数,和下一个待处理的数,循环终止的条件应该和0相关,最后再通过拼接字符串或者其他方式输出我们的余数。
那么我们还没有解决如何得到余数的问题,同样以173除2为例
实现字符串的数字运算,其实和我们运算的过程一样,从字符串第一位开始取每一位除以m,那么如果遇到有余数的怎么处理呢?在这里17除2余1,可以发现下一步是13除2,因此余数参与下一次运算的方式应该是1*10+str[i],str[i]是字符串的下一位,而10其实就取决于我们原来输入数字的基数,这里是十进制。那么173%2的结果是多少呢,其实就是最后一步运算得到的余数1。
具体实现代码如下:
下面以 n = "173" 做示例,演示在 base=2 时每轮循环的关键变化过程:
1) 初始:
num = "173"
base = 2
ans = ""
2) 第一次循环:
- remainder 从 0 开始。
- 遍历 "173":
1) 字符 '1' → digit = 1 + 0*10 = 1
分子/2 = 0(作为 temp 中的字符),分母余数 remainder = 1
temp = "0"
2) 字符 '7' → digit = 7 + 1*10 = 17
分子/2 = 8,余数 remainder = 1
temp = "08"
3) 字符 '3' → digit = 3 + 1*10 = 13
分子/2 = 6,余数 remainder = 1
temp = "086"
- 去除 temp 前导零 → temp = "86"
- num = "86"
- ans += "1" (因为 remainder=1)
- ans = "1"
3) 第二次循环 (num="86"):
- remainder 重置为 0。
- 遍历 "86":
1) '8' → digit = 8 + 0*10 = 8
分子/2 = 4,余数 remainder=0
temp = "4"
2) '6' → digit = 6 + 0*10 = 6
分子/2 = 3,余数 remainder=0
temp = "43"
- temp 不需去前导零 → "43"
- num = "43"
- ans += "0" (remainder=0)
- ans = "10"
4) 第三次循环 (num="43"):
- remainder = 0
- 遍历 "43":
1) '4' → digit = 4,temp="2",remainder=0
2) '3' → digit = 3+0*10=3,3/2=1,remainder=1,temp="21"
- 去除前导零不变 → "21"
- num = "21"
- ans += "1"
- ans = "101"
5) 第四次循环 (num="21"):
- remainder = 0
- 遍历 "21":
1) '2' → digit=2,2/2=1,remainder=0,temp="1"
2) '1' → digit=1+0*10=1,1/2=0,remainder=1,temp="10"
- 去除开头 0 → temp="10" (这里不需要去除,因为开头不是 '0')
- num = "10"
- ans += "1"
- ans = "1011"
6) 第五次循环 (num="10"):
- remainder = 0
- 遍历 "10":
1) '1' → digit=1+0*10=1,1/2=0,remainder=1,temp="0"
2) '0' → digit=0+1*10=10,10/2=5,remainder=0,temp="05"
- 去除前导零 → "5"
- num = "5"
- ans += "0"
- ans = "10110"
7) 第六次循环 (num="5"):
- remainder=0
- '5' → digit=5+0*10=5,5/2=2,remainder=1,temp="2"
- 去前导零不变 → "2"
- num="2"
- ans += "1"
- ans="101101"
8) 第七次循环 (num="2"):
- remainder=0
- '2' → digit=2+0=2,2/2=1,remainder=0,temp="1"
- num="1"
- ans+="0"
- ans="1011010"
9) 第八次循环 (num="1"):
- remainder=0
- '1' → digit=1+0=1,1/2=0,remainder=1,temp="0"
- 去前导零后 temp="",用三元运算处理后 num="0"
- ans+="1"
- ans="10110101"
10) num="0" → 跳出循环,反转 ans:
"10110101" 反转后得到 "10101101"
因此最终输出的二进制结果是 "10101101"。
#include <bits/stdc++.h>
using namespace std;
string to_base(int base, string num)
{
// 当输入数为 "0" 时,直接返回
if (num == "0") {
return "0";
}
string ans;
while (num != "0")
{
int remainder = 0;
string temp;
// 模拟大整数除以 base
for (char c : num)
{
int digit = (c - '0') + remainder * 10;
temp.push_back((digit / base) + '0');
remainder = digit % base;
}
// 去除除法结果前导零
while (!temp.empty() && temp[0] == '0')
{
temp.erase(temp.begin());
}
// 如果删完后字符串为空,则表示剩余值为 0
num = temp.empty() ? "0" : temp;
ans.push_back(char(remainder + '0'));
}
// 翻转得到最终结果
reverse(ans.begin(), ans.end());
return ans;
}
int main()
{
string n;
while (cin>>n)
{
cout << to_base(2, n) << "\n";
}
return 0;
}
前缀和和差分
求和(前缀和)
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=2e5+10; // 允许最大输入的规模
ll a[N];
ll s[N];
int main()
{
ll n;
cin >> n; // 输入 n
for (int i = 1; i <= n; i++) {
cin >> a[i]; // 输入 a 数组
s[i] = s[i - 1] + a[i]; // 计算前缀和
}
ll sum = 0;
// 从后往前计算两两乘积和
for (int i = n; i >= 2; i--) {
sum += a[i] * s[i - 1]; // 每个 a[i] 乘以前面所有数的和
}
cout << sum; // 输出结果
return 0;
}
重新排序
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 3;
int a[N], cnt[N]; // a[]:读入数组; cnt[i]:第i个数被加的次数
int main() {
int n;
scanf("%d", &n); // 输入数组长度
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]); // 输入数组元素
int m;
scanf("%d", &m); // 输入区间操作次数
long long ans1 = 0, ans2 = 0; // ans1: 原区间和; ans2: 新区间和
// 处理每个区间操作
while (m--) {
int L, R;
scanf("%d%d", &L, &R); // 输入区间 [L, R]
for (int i = L; i <= R; i++)
cnt[i]++; // 第i个数被加了一次,累计加了多少次
}
// 计算原数组的加权和
for (int i = 1; i <= n; i++)
ans1 += (long long)a[i] * cnt[i];
// 排序数组和 cnt 数组
sort(a + 1, a + 1 + n);
sort(cnt + 1, cnt + 1 + n);
// 计算新的加权和
for (int i = 1; i <= n; i++)
ans2 += (long long)a[i] * cnt[i];
// 输出结果
printf("%lld\n", ans2 - ans1); // 输出新区间和与原区间和的差值
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
long long a[N], d[N], cnt[N];
long long n, m;
long long ans1 = 0, ans2 = 0;
int main() {
cin >> n;
// 输入数组 a[]
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
cin >> m;
// 区间加法操作,使用差分数组 d[]
for (int i = 1; i <= m; i++) {
int l, r;
cin >> l >> r;
d[l]++; // 区间起始加 1
if (r + 1 <= n) d[r + 1]--; // 区间结束后一个位置减 1
}
// 计算差分数组 d[] 对应的前缀和数组 cnt[]
for (int i = 1; i <= n; i++) {
cnt[i] = cnt[i - 1] + d[i]; // 计算每个位置被加的次数
}
// 计算原始的加权和 ans1
for (int i = 1; i <= n; i++) {
ans1 += cnt[i] * a[i];
}
// 对数组 a[] 和 cnt[] 排序
sort(a + 1, a + n + 1);
sort(cnt + 1, cnt + n + 1);
// 计算排序后的加权和 ans2
for (int i = 1; i <= n; i++) {
ans2 += cnt[i] * a[i];
}
// 输出差值
cout << ans2 - ans1 << endl;
return 0;
}
光骓者的荣耀(一维前缀和)
传送门
题目的核心在于:你只可以从城市 1 走到城市 n,并且中途可以在任意一次使用传送器,将某一段路程“跳过”而不耗时(且若要传送的目标位置超出边界,则直接到达边界城市 n 或 1)。因此求解思路主要是:
- 先用前缀和 (prefix sum) 来方便计算任意一段相邻城市路程的总耗时。
- 对于每个可能的传送起点 i,需要计算:“如果从 i 号城市传送至 i+k 或边界 n,这将跳过的那一段路程的耗时是多少?” 并取其中最大的跳过路段耗时作为最佳传送方案。
- 在遍历过程中,保留“最大可跳过路段耗时”,然后用“总耗时 - 该最大跳过值”即为答案。
实现时需要特别注意越界处理(当 i+k > n 时,应直接跳到 n)以及边界时的下标索引。只要能正确计算所有可能使用传送器的位置,就能找到最佳跳过路段并得到最小总运行时间。
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e6+10;
ll n,k;
ll a[N];
ll prefix[N];
int main()
{
cin>>n>>k;
// 读入边的权值 a[i] 表示从 i -> i+1 的时间消耗
// prefix[i] 存储 a[1] + a[2] + ... + a[i]
for(int i=1;i<n;i++)
{
cin>>a[i];
prefix[i] = prefix[i - 1] + a[i];
}
// 不使用传送器时,总耗时为 prefix[n-1]
// 下面找可跳过的最大路段(长度最多为 k,或直到 n)
ll space=INT_MIN;
for(int i=1;i<n;i++)
{
int j=min(n,i+k);
// 跳过从城市 i 到城市 j 的行程
// 相当于跳过 a[i] + a[i+1] + ... + a[j-1]
// 这段费用是 prefix[j-1] - prefix[i-1]
ll cur_skip=prefix[j-1]-prefix[i-1];
space=max(space,cur_skip);
}
// 答案 = 不使用传送器的总费用 - 能跳过的最大费用
cout<<prefix[n-1]-space;
return 0;
}
领地的选择(二维前缀和)
正方形区域的坐标计算:
- 当你要选择一个大小为
C x C
的正方形时,左上角是(x1, y1)
。 - 右下角的坐标会是
(x1 + C - 1, y1 + C - 1)
,因为从左上角到右下角要移动C-1
步才能到达。- 假设
C = 2
,那么从(x1, y1)
到右下角的坐标是(x1+1, y1+1)
,即C-1
步的移动。
- 假设
举个例子
假设:
N = 4, M = 4, C = 2
地图值:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
我们想选择一个大小为 2x2
的正方形,假设左上角坐标为 (1, 1)
。
- 这个正方形的左上角就是
(1, 1)
。 - 右下角是
(1+2-1, 1+2-1) = (2, 2)
。
即正方形范围是从 (1, 1)
到 (2, 2)
。
x1 + c - 1
和y1 + c - 1
这样计算是因为从左上角(x1, y1)
到右下角需要C - 1
步,所以右下角是x1 + C - 1
和y1 + C - 1
。- 这种方式适用于任意大小的矩形或正方形区域,不仅限于
C x C
。
这种方法在计算矩形区域和时非常重要,确保我们能正确计算任意正方形区域的得分。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e3 + 10;
// 全局变量声明:
int n, m, c; // 分别为地图的尺寸 n×m 以及边长为 c 的正方形
ll max_x = INT_MIN, max_y = INT_MIN; // 用于记录最大价值子矩阵的左上角 x 和 y 坐标
ll price = INT_MIN; // 用于记录目前找到的最大价值和
ll a[N][N], s[N][N]; // a[][] 存储地图的价值,s[][] 存储前缀和
int main()
{
// 读入 n, m, c
cin >> n >> m >> c;
// 读入 n 行,每行 m 个值,存入 a 数组
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
cin >> a[i][j];
}
}
// 计算前缀和:
// s[i][j] = 上边前缀和 + 左边前缀和 - 左上角重叠部分前缀和 + 当前值
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j];
}
}
// 枚举所有可能的 c×c 正方形子区域
// 左上角的坐标为 (x1, y1),右下角为 (x1 + c - 1, y1 + c - 1)
for(int x1 = 1; x1 + c - 1 <= n; x1++)
{
for(int y1 = 1; y1 + c - 1 <= m; y1++)
{
ll x2 = x1 + c - 1;
ll y2 = y1 + c - 1;
// 使用前缀和快速计算子矩阵 (x1, y1) 到 (x2, y2) 的元素和
ll sum = s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
// 如果当前计算的子矩阵价值更大,则更新最大价值和以及左上角坐标
if(sum > price)
{
price = sum;
max_x = x1;
max_y = y1;
}
}
}
// 输出最大的 c×c 子矩阵左上角坐标
cout << max_x << " " << max_y;
return 0;
}
地毯(差分)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1000 + 10;
// 全局变量 n, m 代表了 n × n 的棋盘大小以及 m 个操作数
ll n, m;
// a[][] 用来存储二维差分初胚
// s[][] 用来存储最终前缀和数组
ll a[N][N];
ll s[N][N];
// insert 函数用于在 [x1..x2, y1..y2] 区域内做差分 +1
void insert(int x1, int y1, int x2, int y2)
{
// 在左上角 (x1, y1) 加 +1
a[x1][y1] += 1;
// 在 (x2+1, y1) 加 -1,负责上边界之后的一列抵消
a[x2 + 1][y1] -= 1;
// 在 (x1, y2+1) 加 -1,负责左边界之后的一行抵消
a[x1][y2 + 1] -= 1;
// 在 (x2+1, y2+1) 加 +1,用来把多减掉的部分再加回来
a[x2 + 1][y2 + 1] += 1;
}
int main()
{
// 读入 n (棋盘大小) 和 m (操作数)
cin >> n >> m;
// 连续读入 m 个操作,每个操作代表在 [x1..x2, y1..y2] 区域内 +1
for(int i = 1; i <= m; i++)
{
ll x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
insert(x1, y1, x2, y2);
}
// 通过前缀和 s[][] 将差分数组 a[][] 累加成实际结果
// s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + a[i][j]
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
s[i][j] = s[i - 1][j]
+ s[i][j - 1]
- s[i - 1][j - 1]
+ a[i][j];
}
}
// 输出最终的覆盖次数,即每个格子被加了几次
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= n; j++)
{
cout << s[i][j] << " ";
}
cout << endl;
}
return 0;
}
卡牌游戏
- 每次读取新卡牌后,s = s + x 就是我们对卡牌序列“从左到右”的一个简单累计,如果这个累计和依旧大于 0,就意味着在这个时点继续累加是“有收益”的。
- 条件 (i != 1) 则避免了在只有第一张卡、尚未形成任何合并时就直接统计到答案中——可能是出于某种初始状态或防止将第一张卡直接合并的考虑。
- 一旦 s <= 0,就不再将其加到 ans 中,而是继续累加下一张卡牌,直到再次出现正数累计和。
- 最终 ans 记录了这期间出现的“正区间和”累积总和,等价于把序列中每一个“保持累加和为正的部分”都纳入了总分。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
ll n, x, s = 0, ans = 0;
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> x;
s += x;
// 只要当前累计和s大于0,并且已经不是第一次选择(i != 1)
if (s > 0 && i != 1) {
ans += s;
}
}
cout << ans << endl;
return 0;
}
二分
求阶乘
传送门
假如考虑10!
中质因子2的个数,显然10!
中有因子21的数的个数为5,有因子22的数的个数为1,因此10!
中有质因子的个数为5+2+1=8
;
10! = 10 × 9 × 8 × 7 × 6 × 5 × 4 × 3 × 2 × 1
21有2 4 6 8 10
n = 10,n / 2 = 5,所以 10! 中有 5 个 2 的因子。
更新 n = n / 2 = 10 / 2 = 5。
…
22有4 8
n = 5,n / 2 = 2,所以 10! 中又有 2 个 2 的因子。
更新 n = n / 2 = 5 / 2 = 2。
…
23有8
= 2,n / 2 = 1,所以 10! 中又有 1 个 2 的因子。
更新 n = n / 2 = 2 / 2 = 1。
第四次迭代:
n = 1,n / 2 = 0,停止循环。
n!中有
(
n
/
p
+
n
/
p
2
+
n
/
p
3
+
.
.
.
)
(n/p+n/p^2+n/p^3+...)
(n/p+n/p2+n/p3+...)个质因子p,其中除法均为向下取整。
代码如下
// 计算 n! 中包含多少个质因子 p
ll cal(ll n, ll p) {
ll ans = 0;
while (n) {
ans += n / p; // 将 n 除以 p,得到当前 n 中有多少个 p 作为因子
n /= p;
}
return ans;
}
#include <iostream>
using namespace std;
using ll = long long;
ll k;
// 计算 n! 中包含多少个质因子 p (例如 p=5)
ll cal(ll n, ll p) {
ll ans = 0;
while (n) {
ans += n / p;
n /= p;
}
return ans;
}
// check(mid) 判断 mid! 的末尾 0 的数量是否 >= k
bool check(ll mid) {
ll cnt = cal(mid, 5);
return cnt >= k;
}
int main() {
cin >> k;
ll l = 0, r = 1e19;
// 二分:找最小的 mid 满足 mid! 中 5 的因子数 >= k
while (l < r) {
ll mid = (l + r) >> 1;
if (check(mid)) {
r = mid;
} else {
l = mid + 1;
}
}
// 此时 r 是最小的满足末尾 0 的数量 >= k 的值
// 题目要求:若末尾 0 数量恰好等于 k,则输出;否则输出 -1
if (cal(r, 5) == k) {
cout << r << endl;
} else {
cout << -1 << endl;
}
return 0;
}
青蛙过河
#include <iostream>
using namespace std;
using ll=long long;
const int N=1e5+10;
ll n,x;
ll h[N];
ll s[N];
bool check(ll mid)//mid为跳跃能力
{
/*枚举每个长度为mid的区间
如果 s[left+mid-1]-s[left-1]<2*x
此时说明当前的跳跃能力mid无法跳跃往返2*x次
根据二段性,mid左边的值都无法往返2*x次,此时让l=mid+1
*/
for(int left=1;left<=n-mid;left++)
{
int right=left+mid-1;
//由于一个区间的长度为 mid,
//表示从下标 left 到下标 right(包含 right),
//就有 (right - left + 1) = y,所以可以得到:right = left + mid - 1
if(s[right]-s[left-1]<2*x)
{
return false;
}
}
return true;
}
int main()
{
cin>>n>>x;
for(int i=1;i<=n-1;i++)
{
cin>>h[i];
}
for(int i=1;i<=n;i++)
{
s[i]=s[i-1]+h[i];
}
ll l=0,r=1e9+10;
while(l<r)
{
ll mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<r;
return 0;
}
蓝桥官方题解
管道
- 在 check(mid) 函数中,根据每个要覆盖的目标点 (L, S),先判断如果 mid ≥ S,则计算出能覆盖的区间边界 [l, r];然后将所有可覆盖区间放入数组 q 并排序。
- 通过遍历已排序的 q 来合并所有可能的覆盖区间,查看能否最终从 1 连续覆盖到 m。若可以,则说明 mid 时间足够,否则需要更大的 mid。
- 在主函数中使用二分搜索,逐渐缩小能够满足覆盖条件的 mid,最终输出能覆盖区间 [1, m] 的最小时间。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#define x first
#define y second
using namespace std;
using ll=long long ;
typedef pair<int, int> PII;
const int N=1e5+3;
int n,m;
PII w[N],q[N];
bool check(int mid)
{
int cnt=0;
for(int i=1;i<=n;i++)
{
int L=w[i].x,S=w[i].y;
if(mid>=S)
{
int t=mid-S;
int l=max(1,L-t),r=min((ll)m,(ll)L+t);
q[cnt++]={l,r};
}
}
sort(q,q+cnt);
int st=-1,ed=-1;
for(int i=0;i<cnt;i++)
{
if(q[i].x<=ed+1)ed=max(ed,q[i].y);
else st=q[i].x,ed=q[i].y;
}
return st==1&&ed==m;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>w[i].x>>w[i].y;
ll l=0,r=2e9;
while(l<r)
{
ll mid=l+r>>1;
if(check(mid))r=mid;
else l=mid+1;
}
cout<<r;
return 0;
}
技能升级
暴力思路
将每个技能升级看成一个倒序的等差数列,将所有技能升级的等差数列都放在一个集合中,在对集合从大到小排序,取前m个数即可,但是原问题有限制,即左边的序列必须连续取,只取前m个数只能说明答案的总和<=s
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m;
int a[N], b[N];
/*
对每个技能,若 a[i] ≥ mid,则从 a[i]“递减”到小于 mid 的过程中,需要的升级次数为 (a[i] - mid) / b[i] + 1。
“+1” 表示只要 a[i] ≥ mid,就至少计为一次可用增益。
• 将所有此类技能的升级次数相加,若总和 ≥ m,则说明以 mid 为阈值时能够满足 m 次。
*/
bool check(int mid)
{
LL res = 0;
for (int i = 0; i < n; i ++ )
if (a[i] >= mid)
res += (a[i] - mid) / b[i] + 1;
return res >= m;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++ ) scanf("%d%d", &a[i], &b[i]);
int l = 0, r = 1e6;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
/*
end = a[i] - (c - 1) × b[i] 为这个技能最后一次实际增益的数值;
这相当于一个等差数列,求和为 (a[i] + end) × c / 2;
将升级次数 c 累加到 cnt,总和累加到 res。
*/
LL res = 0, cnt = 0;
for (int i = 0; i < n; i ++ )
if (a[i] >= r)
{
int c = (a[i] - r) / b[i] + 1;
int end = a[i] - (c - 1) * b[i];
cnt += c;
res += (LL)(a[i] + end) * c / 2;
}
printf("%lld\n", res - (cnt - m) * r);//相同的m可能有多个,cnt会多加一些值,这时候就需要减去
return 0;
}
分巧克力
#include <iostream>
using namespace std;
int const N = 100010;
int w[N], h[N];//存储长、宽
int n, k;
bool chack(int a)
{
int num = 0;//记录分成长度为 a 的巧克力数量
for (int i = 0; i < n; i++)
{
num += (w[i] / a) * (h[i] / a);//每一大块可以分成的边长为 a 的巧克力数量
if (num >= k) return true;//大于要求数量,返回真
}
return false;
}
int main()
{
cin >> n >> k;
for (int i = 0; i < n; i++) cin >> h[i] >> w[i];
int l = 1, r = 1e5;//小巧克力数量边长一定在 1 -- 100000 之间
while (l < r)//二分小巧克力边长范围,找到符合要求的最大值
{
int mid = l + (r - l + 1 >> 1);//因为l = mid ,所以 mid 取 l + r + 1 >> 1,为了防止加和越界,改写成 l + (r - l + 1 >> 1)
if (chack(mid)) l = mid;
else r = mid - 1;
}
cout << r;
}
跳石头
#include <bits/stdc++.h>
using namespace std;
using ll=long long ;
const int P=5e4+3;
int a[P];
int L,N,M;
bool check(int mid)
{
int cnt = 0; // 已经移除的石头数量
int now = 0; // 当前“落脚”的石头位置,初始为起点位置 0
for(int i = 1; i <= N; i++)
{
// 如果当前石头与前一次落脚点 now 之间的距离 < mid,
// 那么为了保证最短跳跃距离至少为 mid,只能移除这块石头
if(a[i] - now < mid) cnt++;
else {
// 否则就跳到这块石头上,并更新 now
now = a[i];
}
}
// 只要移除的石头数不超过 M,就表示成功
return cnt <= M;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>L>>N>>M;
for(int i=1;i<=N;i++)cin>>a[i];
a[++N]=L;
int l=0,r=L;//首尾都有石头
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid))l=mid;
else
r=mid-1;
}
cout<<l;
return 0;
}
可凑成的最大花束数
传送门
思路
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=2e5+10;
ll n,k;
ll a[N];
bool check(ll x)//二分可以打包的花束数
{
ll res=0;
for(int i=1;i<=n;i++)
{
res+=min(x,a[i]);
}
return res/x>=k;
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
ll l=0,r=2e14;
while(l<r)
{
ll mid=(l+r+1)>>1;
if(check(mid))l=mid;//如果mid满足,则mid-1也满足
else r=mid-1;
}
cout<<l;
return 0;
}
最大通过数
-
先对每个数组分别求前缀和:
• sa[i] 表示左边至第 i 关(1 ~ i)所需的能源总和;
• sb[i] 表示右边至第 i 关(1 ~ i)所需的能源总和。
这样可以用常数时间计算某段区间消耗,从而快速判断是否可行。 -
将“总关卡数”视作一个二分搜索的问题:猜一个值 mid,表示两人能合计通过多少关卡(i + (mid - i) = mid),并进行可行性判断:
• 遍历洛洛通过 i 关 (0 ≤ i ≤ mid),晶晶通过 (mid - i) 关;
• 若 sa[i] + sb[mid - i] 在某种组合下 ≤ k,即可行,否则不可行。
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=2e5+10;
ll n,m,k;
ll a[N],sa[N],b[N],sb[N];
bool check(ll mid)//二分关卡数
{
ll num=1e18;
for(ll i=0;i<=mid;i++)
{
if(i<=n&&(mid-i)<=m)
{
num=min(num,sa[i]+sb[mid-i]);
}
}
return num<=k;
}
int main()
{
cin>>n>>m>>k;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
sa[i]=sa[i-1]+a[i];
}
for(ll i=1;i<=m;i++)
{
cin>>b[i];
sb[i]=sb[i-1]+b[i];
}
ll l=0,r=2e14;
while(l<r)
{
int mid=(l+r+1)>>1;
if(check(mid))l=mid;
else r=mid-1;
}
cout<<l;
return 0;
}
蓝桥A梦做铜锣烧
- 首先根据输入字符串统计每份成品需要的面包量 (cnt1) 和馅料量 (cnt2)。
- 在二分范围 [0, 1e14] 内,用 check 函数来判断如果要做 mid 份成品,所需要的面包和馅料如果不足,则计算购买它们的总花费 sum 是否超出预算 n。
- 如果花费不超预算 (sum <= n),说明可以做 mid 份成品,于是把左边界调整到 mid;否则把右边界缩小到 mid - 1。
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
ll n,a,b,c,d,cnt1,cnt2;
string s;
bool check(ll mid)
{
// sum 用于记录为做 mid 个成品需要支付的额外花费
ll sum = 0;
// 需要的面包总数
ll needB = mid * cnt1;
// 如果当前家里的面包不足,则计算需要额外购买的数量
if (needB > a)
{
sum += (needB - a) * c;
}
// 需要的馅料总数
ll needF = mid * cnt2;
// 如果当前家里的馅料不足,则计算需要额外购买的数量
if (needF > b)
{
sum += (needF - b) * d;
}
// 判断花费是否在预算 n 以内
return sum <= n;
}
int main()
{
cin>>n;
cin>>s;
cin>>a>>b;
cin>>c>>d;
for(auto c:s)
{
if(c=='b')
{
cnt1++;
}
else if(c=='f')
{
cnt2++;
}
}
ll l=0,r=1e14;
while(l<r)
{
ll mid=l+r+1>>1;
if(check(mid))l=mid;
else r=mid-1;
}
cout<<l;
return 0;
}
肖恩的苹果林
传送门
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e5+10;
int n,m;
int a[N];
bool check(int mid)
{
int cnt=1;
int now=a[1];
for(int i=2;i<=n;i++)
{
if(a[i]-now>=mid)
{
now=a[i];
cnt++;
}
}
return cnt>=m;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a+1,a+1+n);
int l=0,r=1e9;
while(l<r)
{
int mid=(l+r+1)>>1;
if(check(mid))l=mid;
else
r=mid-1;
}
cout<<l;
return 0;
}
妮妮的月饼工厂
- 定义一个函数 check(mid),用来判断如果每个原材料都切割成高度为 mid 的若干块(每块宽度不变),总共能否切出至少 K 块月饼。
- 在主函数中,用二分法在 [0, 10^9] 区间查找满足条件的最高高度:
- 每次取 mid = (l + r + 1) >> 1,并调用 check(mid)。
- 如果能切出 K 块 (check(mid) == true),说明还可以尝试更高的月饼高度,令 l = mid;否则缩小范围令 r = mid - 1。
- 二分结束后,如果找到可行解,就输出该最高高度;否则输出 -1。
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const ll N=1e5+10;
ll n,k;
ll a[N];
bool check(ll mid)//二分高度
{
ll cnt=0;
for(ll i=1;i<=n;i++)
{
cnt+=a[i]/mid;
if(cnt >= k) return true;
}
return cnt>=k;
}
int main()
{
cin>>n>>k;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a+1,a+1+n);
ll l=0,r=1e9;
bool flag=false;//注意-1的情况
while(l<r)
{
ll mid=(l+r+1)>>1;
if(check(mid))
{
flag=true;
l=mid;
}
else r=mid-1;
}
if(flag)
{
cout<<l;
}
else
{
cout<<-1;
}
return 0;
}
一元三次方程求解
传送门
通过在区间 [-100.0, 100.0] 上以 0.01 步长枚举 x 值,并计算 f(x) = ax³ + bx² + c*x + d 来判断在该点处函数值是否接近 0(绝对值小于 1e-7)。如果满足条件,则认为这个 x 属于方程的实根
#include<iostream>
#include<cmath>
using namespace std;
double a,b,c,d;
const double eps = 1e-7;
double y (double x) {
return a*x*x*x+b*x*x+c*x+d;
}
int main () {
cin >> a >> b >> c >> d;
for (double x = -100.0;x <= 100;x +=0.01) {
if (abs(y(x)) <= eps) {
printf("%.2f ",x);
}
}
cout << endl;
return 0;
}
123
1. 小区间的构成
假设数列的构成是如下形式:
- 第 1 个区间包含 1 个元素(
1
)。 - 第 2 个区间包含 2 个元素(
1 2
)。 - 第 3 个区间包含 3 个元素(
1 2 3
)。 - 第 4 个区间包含 4 个元素(
1 2 3 4
)。 - …
第 i
个小区间包含 i
个元素。我们将这些小区间连起来形成整个数列。
2. 数组 a[j]
的定义
数组 a[j]
表示前 j
个小区间的总元素数,同时也能表示每个小区间的和。例如:
a[1] = 1
(表示前 1 个小区间有 1 个元素)a[2] = 1 + 2 = 3
(表示前 2 个小区间共有 3 个元素)a[3] = 1 + 2 + 3 = 6
(表示前 3 个小区间共有 6 个元素)a[4] = 1 + 2 + 3 + 4 = 10
(表示前 4 个小区间共有 10 个元素)
注意,数组 a[j]
是单调递增的,因为每个小区间的元素个数都在增加。
关键点:k = i - a[j]
- 数列中的位置
i
是在第j+1
个区间中的某个元素。 - 前
j
个区间包含了a[j]
个元素,也就是说,第j+1
个区间的第一个元素出现在位置a[j] + 1
。
因此,位置 i
在第 j+1
个区间的具体位置是:
- 第
j+1
个区间的第k
个元素:k
就是位置i
相对于第j+1
个区间开始位置的偏移量。
由于前 j
个区间包含了 a[j]
个元素,第 j+1
个区间从位置 a[j] + 1
开始。所以位置 i
在第 j+1
个区间中的具体位置是:
k = i - a[j]
#include <iostream>
using namespace std;
using ll=long long;
const int N=1414215;
ll a[N],s[N];
ll persum(ll i)
{
ll l=0,r=N;
while(l<r)
{
ll mid=(l+r+1)>>1;
if(a[mid]<i)l=mid;
else r=mid-1;
}
return s[l]+a[i-a[l]];
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
for(int i=1;i<N;i++)
{
a[i]=a[i-1]+i;
s[i]=s[i-1]+a[i];
}
int t;
cin>>t;
while(t--)
{
ll l,r;
cin>>l>>r;
cout<<persum(r)-persum(l-1)<<endl;
}
return 0;
}
贪心
部分背包问题
传送门
按单位价格排序,最贵的先拿,便宜的后拿。
#include <bits/stdc++.h>
using namespace std;
int n,c;
struct gold
{
double w,v,p;// w: 重量, v: 价值, p: 单位价值
}a[105];
bool cmp(gold a,gold b)
{
return a.p>b.p; // 按单位价值从大到小排序
}
int main()
{
cin>>n>>c;
for(int i=1;i<=n;i++)
{
cin>>a[i].w>>a[i].v;
a[i].p=a[i].v/a[i].w;// 计算单位价值
}
sort(a+1,a+1+n,cmp);// 按单位价值从大到小排序
double ans=0;// 记录最大价值
for(int i=1;i<=n;i++)
{
if(c>=a[i].w)// 如果当前堆金币的重量小于背包剩余容量
{
ans+=a[i].v; // 增加金币价值
c-=a[i].w;// 背包容量减少
}
else // 如果当前堆金币的重量大于背包剩余容量
{
ans+=c*a[i].p;// 装入部分金币,按单位价值装
break;// 背包已满,退出
}
}
cout<<fixed<<setprecision(2)<<ans;
return 0;
}
线段覆盖(不相交区间问题)
传送门
思路
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n;
pair<int, int> t[N]; // 存储每个比赛的开始时间和结束时间
// 排序规则:按结束时间升序
bool cmp(const pair<int, int>& a, const pair<int, int>& b)
{
return a.second < b.second;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> t[i].first >> t[i].second; // 读入比赛的开始和结束时间
}
// 按照结束时间从小到大排序
sort(t + 1, t + 1 + n, cmp);
int cnt = 0;
int ed = -2e9; // 记录最后一个选择比赛的结束时间,初始化为一个极小值
for (int i = 1; i <= n; i++)
{
if (ed <= t[i].first) // 如果当前比赛的开始时间 >= 上一个选择的结束时间
{
cnt++; // 选择当前比赛
ed = t[i].second; // 更新最后选中的比赛的结束时间
}
}
cout << cnt;
return 0;
}
区间覆盖
- 题目描述
给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。
输出最少区间数,如果无法完全覆盖则输出 −1。
- 样例
1 5
3
-1 3
2 4
3 5
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
struct Range
{
int l, r;
bool operator< (const Range &W)const
{
return l < W.l; // 按区间的左端点升序排序
}
}range[N];
int main()
{
int st, ed;
scanf("%d%d", &st, &ed); // 读取目标区间 [st, ed]
scanf("%d", &n); // 读取区间的数量
for (int i = 0; i < n; i ++ )
{
int l, r;
scanf("%d%d", &l, &r); // 读取每个子区间的 [l, r]
range[i] = {l, r}; // 存储每个子区间
}
sort(range, range + n); // 按左端点升序排序
int res = 0; // 记录选择的子区间数量
bool success = false; // 标记是否成功覆盖区间
for (int i = 0; i < n; i ++ )
{
int j = i, r = -2e9; // r 记录当前能够覆盖的最远右端点
while (j < n && range[j].l <= st) // 寻找所有能够覆盖当前 st 的区间
{
r = max(r, range[j].r); // 找到右端点最远的区间
j ++ ;
}
if (r < st) // 如果找不到能够覆盖当前 st 的区间
{
res = -1; // 无法覆盖,返回 -1
break;
}
res ++ ; // 选择一个子区间
if (r >= ed) // 如果已经覆盖到目标区间的结束位置
{
success = true; // 成功覆盖
break;
}
st = r; // 更新当前覆盖的起始点
i = j - 1; // 将 i 更新到新的子区间位置
}
if (!success) res = -1; // 如果没有成功覆盖
printf("%d\n", res); // 输出结果
return 0;
}
区间覆盖(加强版)
/*
假设我们有一个区间 [1, 5],
其中左端点为 1,右端点为 5,
直接计算 r - l 会得到 5 - 1 = 4,这表示从 1 到 5 的间隔长度。
但实际上,区间 [1, 5] 包括 1, 2, 3, 4, 5 五个数,
因此区间的长度应该是 5,
所以需要加上 1 来包含右端点。
*/
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 10;
ll n;
pair<ll, ll> a[N]; // 存储每个区间的左端点和右端点
// 按照左端点升序排序,左端点相同的情况下按右端点升序排序
bool cmp(const pair<ll, ll>& a, const pair<ll, ll>& b)
{
if (a.first != b.first)
{
return a.first < b.first;
}
else
{
return a.second < b.second;
}
}
int main()
{
cin >> n; // 读入区间数量
for (int i = 1; i <= n; i++)
{
cin >> a[i].first >> a[i].second; // 读入每个区间的左端点和右端点
}
sort(a + 1, a + 1 + n, cmp); // 按照区间的左端点排序,左端点相同则按右端点排序
ll ans = a[1].second - a[1].first + 1; // 第一个区间的贡献
ll rr = a[1].second; // 当前的最右端点
// 从第二个区间开始处理
for (int i = 2; i <= n; i++)
{
// 如果当前区间的左端点大于当前最右端点,说明没有交集,直接加上当前区间的长度
if (a[i].first > rr)
{
ans += a[i].second - a[i].first + 1;
}
else if (a[i].second > rr) // 如果当前区间的右端点大于最右端点,说明有交集,合并区间
{
ans += a[i].second - rr; // 只加上右端点与当前最右端点之间的长度
}
// 更新最右端点
rr = max(rr, a[i].second);
}
cout << ans << endl; // 输出合并后的总长度
return 0;
}
填充
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int main()
{
string s;
cin>>s;
int len=s.size();
int ans=0;
for(int i=0;i<len-1;i++)
{
if(s[i]==s[i+1])
{
ans++;
i++;
}
else if(s[i]=='?'||s[i+1]=='?')
{
ans++;
i++;
}
}
cout<<ans;
return 0;
}
买二赠一
- 先将所有商品的价格从大到小进行排序,这样在后续处理时更方便从高价到低价依次尝试购买或免费领取。
- 数组 st[] 用于记录某件商品是否已“使用”,包括购买或免费获取;ans 用于累加购买需要花费的总价。
- 每遍历一件商品,如果还未使用,则将其加入总价并标记为使用;这会使当前的购买计数(cnt)加 1。
- 当购买计数达到 2 时,说明可以领取一次价格不超过这两件商品中较便宜那件“价格 P”的商品。代码里以 a[i]/2 作为阈值 x 再去寻找能免费领取的商品;找到了合适的商品 pos 后,就将该商品标记为已使用。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 5e5 + 10;
bool st[N]; // 标记每个商品是否已购买或已免费获得
ll a[N]; // 存储商品的价格
ll n, ans; // n 是商品数量,ans 是最终花费
bool cmp(const ll &u, const ll &v) {
return u > v;
}
int main() {
cin >> n;
for (ll i = 1; i <= n; i++) {
cin >> a[i];
}
// 降序排列
sort(a + 1, a + 1 + n, cmp);
ll cnt = 0; // 计数器,用来控制每购买两件商品后进行一次赠品选择
for (ll i = 1; i <= n; i++) {
if (!st[i]) { // 如果该商品还没有被购买或免费获得
ans += a[i]; // 购买该商品,增加花费
st[i] = true; // 标记该商品为已购买
cnt++; // 增加已购买商品计数
// 每购买两件商品后进行赠品选择
if (cnt == 2) {
cnt = 0; // 重置计数器
ll x = a[i] / 2; // 根据当前商品的价格,计算赠品的最大价格
// 查找第一个价格不超过 x 的商品
ll pos = lower_bound(a + i + 1, a + n + 1, x) - a;
// 跳过已标记为已购买的商品
while (pos <= n && st[pos]) {
pos++;
}
// 标记为已免费获得
if (pos <= n) {
st[pos] = true;
}
}
}
}
cout << ans;
return 0;
}
购物(可组成范围)
#include <bits/stdc++.h>
using namespace std;
/*
1. 将硬币按面值从小到大排序。
2. 维护一个可组成的连续区间 [1..m],初始 m = 0 表示当前无法组成任何值。
3. 每次从最大的硬币往回找,如果面值 <= m + 1,就可以将当前硬币加进来,使得可组成区间延伸到 m + price[i]。
- m + 1 表示“下一段期望能覆盖的最小缺口”。
4. 每次加入一个硬币,都使硬币计数 cnt++,并更新可组成区间上限 m。
5. 若遍历完仍找不到能延伸区间的硬币(flag 仍为 true),说明无法按需求覆盖 [1..X],输出 -1。
6. 只要当前位置 m >= x,说明 [1..x] 全部可组成,输出 cnt。
*/
int x, n;
int price[15];
int main()
{
cin >> x >> n;
// 读入并排序硬币面值
for(int i = 1; i <= n; i++)
{
cin >> price[i];
}
sort(price + 1, price + 1 + n);
// m 表示当前能组成的最大面值范围为 [1..m]
int m = 0;
// cnt 记录硬币数量
int cnt = 0;
// 不断尝试扩张 [1..m]
while(true)
{
// 如果能覆盖到 x,就直接输出答案
if(m >= x)
{
cout << cnt;
return 0;
}
else
{
// 每次增加一个硬币
cnt++;
}
bool flag = true; // 标记本次循环是否有找到合适的硬币
// 从大面值到小面值寻找能拓展区间的硬币
for(int i = n; i >= 1; i--)
{
// 只要硬币面值 <= m + 1,就能将连续区间向后扩张
if(price[i] <= m + 1)
{
m += price[i]; // 更新区间最大范围
flag = false;
break;
}
}
// 如果 flag 一直为 true,表示找不到可以扩张区间的硬币,无法完成目标
if(flag)
{
cout << -1 << endl;
return 0;
}
}
return 0;
}
三国游戏
首先分别计算每个事件对“魏 > 蜀 + 吴”、“蜀 > 魏 + 吴”、“吴 > 魏 + 蜀”的贡献值, 然后对贡献值降序排序。
在排序好的序列中从头开始尝试贪心添加事件, 只要当前总贡献值保持大于 0, 就将该事件加入子集合。
•最后取 3 种情形中能选取事件数的最大值, 如果最大值为 0, 则表示不存在能让任意一方严格超过另两方之和的事件子集, 输出 -1。
/*
// 我们要找出是否存在一个事件子集,使得以下任一条件成立:
// X > Y + Z,或者 Y > X + Z,或者 Z > X + Y。
// 设 Vx[i] = A[i] - B[i] - C[i],Vy[i] = B[i] - A[i] - C[i],Vz[i] = C[i] - A[i] - B[i]。
// 如果某个子集 S 的 sum(Vx[S]) > 0,那么这个事件子集会使 X > Y + Z。
// 我们对 Vy[i] 和 Vz[i] 重复相同的操作。
// 在这三种可能性中,我们取其中最多的事件数。
// 如果没有任何一个子集满足条件,则输出 -1。
//
// 方法(对每个优势数组 V):
// 1) 将 V 的值按降序排序。
// 2) 从最大值到最小值迭代,贪心地选择事件,如果它能保持运行总和大于 0。
// 3) 记录该优势的事件数量。
// 最后,输出三种优势数组中最大事件数,或者如果都为 0,则输出 -1。
*/
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=1e5+100;
ll a[N],b[N],c[N];
ll vx[N],vy[N],vz[N];
ll ans;
ll n;
int solve(ll v[])
{
sort(v+1,v+1+n,greater<ll>());
ll sum=0;
ll cnt=0;
for(int i=1;i<=n;i++)
{
if(sum+v[i]>0)
{
sum+=v[i];
cnt++;
}
else
{
continue; // 如果添加 x 会让总和降到 0 或以下,则跳过它
// 因为我们希望最终的总和大于 0。
}
}
return cnt;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
for(int i=1;i<=n;i++)
{
cin>>b[i];
}
for(int i=1;i<=n;i++)
{
cin>>c[i];
}
for(int i=1;i<=n;i++)
{
vx[i]=a[i]-(b[i]+c[i]); // 魏国的优势
vy[i]=b[i]-(a[i]+c[i]);// 蜀国的优势
vz[i]=c[i]-(a[i]+b[i]);// 蜀国的优势
}
ll ansa=solve(vx);
ll ansb=solve(vy);
ll ansc=solve(vz);
ans=max({ansa,ansb,ansc});
if(ans==0)
{
cout<<-1;// 如果没有一个优势找到严格正的子集,输出 -1
}
else
{
cout<<ans;
}
return 0;
}
平均
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 100;
// n 表示数组长度(并且是 10 的倍数)
// arr[i] 存放所有值等于 i 的元素对应的修改代价
// ans 存放最终的最小总代价
ll n;
vector<ll> arr[N];
ll ans = 0;
int main()
{
// 读入 n
cin >> n;
// 读入 n 个 (ai, bi)
// ai 表示该元素的当前值, bi 表示将该元素改成其它值的代价
// 将 bi 放入 arr[ai] 中, 便于后面针对相同 ai 的元素统一处理
for(int i = 1; i <= n; i++) {
int a, b;
cin >> a >> b;
arr[a].push_back(b);
}
// 遍历所有可能的值 0 ~ 9
for(int i = 0; i <= 9; i++) {
// 先把所有代价按从小到大排序
// 这样如果需要删去(更改)一些数, 会优先删去(更改)代价最小的元素
sort(arr[i].begin(), arr[i].end());
// 当某个值 i 出现次数超过 n / 10 时, 说明需要把多余的元素改成其他值
// 多出的个数为 arr[i].size() - n/10
if((ll)arr[i].size() > n / 10) {
int cnt = arr[i].size() - n / 10;
// 从代价最小的开始累加, 表示优先改掉这些元素
for(int j = 0; j < cnt; j++) {
ans += arr[i][j];
}
}
}
// 输出最终代价和
cout << ans;
return 0;
}
答疑
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define x first
#define y second
const int N = 1e3 + 10;
pair<pair<ll,ll>,pair<ll,ll>> a[N];
/*
在 a[] 中,我们存储:
{ {总时间,进入时间}, {答疑时间,离开时间} }。
其中:
- s_i 表示进入办公室所需时间 (进入时间)
- a_i 表示提问和解答所需时间 (答疑时间)
- e_i 表示离开办公室所需时间 (离开时间,固定为 10000, 20000 或 30000)
- 总时间 = 进入时间 + 答疑时间 + 离开时间
*/
/*
比较时按照以下优先级进行排序:
1. 总时间的升序 (总时间较短的同学先安排)
2. 如果总时间相同,按进入时间升序
3. 如果再次相同,按答疑时间升序
4. 最后才比较离开时间
这样做的目的是让“总时间小”的同学优先完成,以便在计算消息发送时刻之和时整体更小
*/
bool cmp(const pair<pair<ll, ll>, pair<ll, ll>>& p1, const pair<pair<ll, ll>, pair<ll, ll>>& p2)
{
// 按总时间排序
if (p1.x.x != p2.x.x) {
return p1.x.x < p2.x.x;
}
// 如果总时间相同,按进入时间排序
if (p1.x.y != p2.x.y) {
return p1.x.y < p2.x.y;
}
// 如果总时间和进入时间都相同,按答疑时间排序
if(p1.y.x != p2.y.x) {
return p1.y.x < p2.y.x;
}
// 最后比较离开时间
return p1.y.y < p2.y.y;
}
int main()
{
ll n;
cin >> n; // 读入 n,表示有 n 位同学
// 读入每位同学所需的进入时间 s_i、答疑时间 a_i、离开时间 e_i
// 并计算总时间 total = s_i + a_i + e_i 存储在 a[i].x.x
for(ll i = 1; i <= n; i++) {
cin >> a[i].x.y >> a[i].y.x >> a[i].y.y;
a[i].x.x = a[i].x.y + a[i].y.x + a[i].y.y;
}
// 按照比较函数进行排序
sort(a + 1, a + 1 + n, cmp);
/*
流程:
sum 表示已经消耗的时间,用来计算下一位同学开始答疑的起始时间。
ans 表示所有同学发消息的时刻之和 (每位同学的“发消息时间” = 进入 + 答疑),
因为发消息时刻恰好是进入时间 + 答疑时间之后,无需再等待离开时间。
先计算第一位同学的情况,然后循环叠加后续同学的时间。
*/
ll sum = a[1].x.y + a[1].y.x + a[1].y.y; // 第一个同学完成离开后的累计时间
ll ans = a[1].x.y + a[1].y.x; // 第一个同学发消息时刻 (只需要进入 + 答疑)
for(int i = 2; i <= n; i++) {
// 从上一个同学结束后开始,再加上当前同学的进入时间 + 答疑时间
sum += a[i].x.y + a[i].y.x;
// ans 累加当前同学的发消息时间 (相当于起始时刻 + 进入 + 答疑)
ans += sum;
// 如果已经是最后一个同学,就输出 ans 后结束
if(i == n) {
cout << ans << endl;
return 0;
}
// 若不是最后一个同学,还需要加上当前同学的离开时间 (sum 用于给下一个人计算起始点)
sum += a[i].y.y;
}
/*
如果所有同学都循环完,则输出 ans
这里程序逻辑在 i == n 情况时就会 return,不写也可
*/
cout << ans << endl;
return 0;
}
搬砖
#include <iostream>
#include <algorithm>
#define w first
#define v second
using namespace std;
typedef pair<int,int> PII;
const int N = 1010, M = N * 20;
int f[N][M]; // f[i][j]表示前i个物品中,体积不超过j时的最大价值
pair<int, int> p[N]; // 存储每个物品的重量和价值
int n; // 物品数量
// 优先考虑价值与重量差值较大的物品
bool cmp(PII a, PII b)
{
int d1 = a.v - b.w, d2 = b.v - a.w;
return d1 < d2;
}
int main()
{
int m = 0;
cin >> n;
for (int i = 1; i <= n; i++)
{
cin >> p[i].w >> p[i].v;
m += p[i].w;
}
sort(p + 1, p + 1 + n, cmp);
for (int i = 1; i <= n; i++)
{ // 遍历所有物品
for (int j = 0; j <= m; j++)
{ // 遍历所有可能的总重量
f[i][j] = f[i-1][j];
if (j >= p[i].w && p[i].v >= j - p[i].w) //j - w 是当前背包已经占用的重量(即已经选中的砖块总重量),砖的重量和不能超过它自身的价值
{
f[i][j] = max(f[i][j], f[i-1][j - p[i].w] + p[i].v);
}
}
}
int ans = 0;
// 找出所有可能的重量下的最大价值
for (int i = 1; i <= m; i++)
{
ans = max(ans, f[n][i]);
}
cout << ans;
return 0;
}
卡牌游戏
思路简要说明:
- 确定正负号位置:根据题意,位置 1、3、5… 为负号位置,位置 2、4、6… 为正号位置。设负号位置数量为 negCount = (n+1)//2,正号位置数量为 posCount = n//2。
- 对每张卡牌 i,定义:
- negValue_i = min(-A_i, -B_i),表示若放在负号位置时可以选择的最小贡献值。
- posValue_i = min(A_i, B_i),表示若放在正号位置时可以选择的最小贡献值。
- 令 diff_i = negValue_i - posValue_i。将卡牌按照 diff_i 进行升序排序:diff_i 越小,说明这张卡放在负号位置越有利(会使总和变得更小)。
- 选出前 negCount 张卡牌放入负号位置,其余放在正号位置,则结果为:
(∑ posValue_i) + (∑(diff_i) for i in 前 negCount)。
这是因为初始假设全部卡都放在正号位置时,总贡献为 ∑ posValue_i;将其中的某些卡(共 negCount 张)改为负号位置会额外增加 diff_i = (negValue_i - posValue_i)。
举例:
假设我们有 4 张卡片,它们的正反面值和 diff
计算如下:
-
卡片 1:
A1 = -2
,B1 = 5
negValue_1 = min(-(-2), -(5)) = -(-2) = 2
posValue_1 = min(-2, 5) = -2
diff_1 = 2 - (-2) = 4
-
卡片 2:
A2 = 4
,B2 = 3
negValue_2 = min(-(4), -(3)) = -4
posValue_2 = min(4, 3) = 3
diff_2 = -4 - 3 = -7
-
卡片 3:
A3 = -1
,B3 = -3
negValue_3 = min(-(-1), -(-3)) = 1
posValue_3 = min(-1, -3) = -3
diff_3 = 1 - (-3) = 4
-
卡片 4:
A4 = 2
,B4 = 6
negValue_4 = min(-(2), -(6)) = -6
posValue_4 = min(2, 6) = 2
diff_4 = -6 - 2 = -8
初始状态:所有卡片放在正号位置时,sumPosValue
的计算:
sumPosValue = (-2) + 3 + (-3) + 2 = 0
选择前 negCount
张卡片放入负号位置:
-
假设
negCount = 2
(即选择 2 张卡片放入负号位置),我们根据diff[i]
值进行排序:diff_1 = 4
diff_2 = -7
diff_3 = 4
diff_4 = -8
-
按照
diff
排序后,前negCount = 2
个最小的diff
是-8
和-7
,我们将它们加到sumPosValue
上:
ans = sumPosValue + (-8) + (-7) = 0 - 8 - 7 = -15
最终得到的最小结果是 -15
。
#include <bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
long long n;
cin >> n;
vector<long long> negValue(n), posValue(n), diff(n);
for(int i = 0; i < n; i++){
long long A, B;
cin >> A >> B;
long long nv = min(-A, -B); // 放在负号位置时的最小贡献
long long pv = min(A, B); // 放在正号位置时的最小贡献
negValue[i] = nv;
posValue[i] = pv;
diff[i] = nv - pv;
}
// 统计负号位置数量和正号位置数量
long long negCount = (n + 1) / 2; // odd 索引个数
long long posCount = n / 2; // even 索引个数
// 先假设所有牌都放在正号位置
long long sumPosValue = 0;
for(int i = 0; i < n; i++){
sumPosValue += posValue[i];
}
// 根据 diff 从小到大排序
sort(diff.begin(), diff.end());
// 选出前 negCount 张改放在负号位置
long long ans = sumPosValue;
for(int i = 0; i < negCount; i++){
ans += diff[i];
}
cout << ans << "\n";
return 0;
}
小蓝的旅行计划
因为没学过线段树,只能优化到过90%样例
// 思路简述:
// 1. 首先检查是否总需求 > (初始油量 m + 所有站点可以加油的上限之和),若是则直接输出 -1。
// 2. 在遍历每个站点的过程中,先从当前油量中扣除到达该站点所需油量;若不足则需要从之前经过的加油站中
// 以最低价格优先购买(利用小根堆),直到油量满足需求或没有可用的加油站,此时若仍不足则输出 -1。
// 3. 每到一个新的加油站,把它可以提供的油量及其价格加入小根堆中备用。
// 4. 若能成功走完所有站点,则累加费用的总和即为结果。
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
ll n, m;
cin >> n >> m;
vector<ll> dist(n), cost(n), limit(n);
// 读入数据并计算总距离
ll totalDist = 0;
for (ll i = 0; i < n; i++){
cin >> dist[i] >> cost[i] >> limit[i];
totalDist += dist[i];
}
// 如果总需求(总距离)大于初始油量 + 总可加油量,直接无法完成
ll sumLimit = 0;
for (ll i = 0; i < n; i++){
sumLimit += limit[i];
}
if (totalDist > m + sumLimit){
cout << -1 << "\n";
return 0;
}
// 贪心 + 小根堆
// curFuel 表示当前油量, totalCost 表示总花费
ll curFuel = m, totalCost = 0;
// 小根堆储存 (单价, 站点剩余可卖油量);cost越小优先级越高
priority_queue<pair<ll, ll>, vector<pair<ll, ll>>, greater<pair<ll, ll>>> pq;
for (ll i = 0; i < n; i++){
// 先扣除到达当前站点的油量
curFuel -= dist[i];
// 如果油量不足,需从之前的加油站购买
while (curFuel < 0 && !pq.empty()){
pair<ll, ll> p = pq.top();
pq.pop();
ll c = p.first;
ll remain = p.second;
// 买多少升:要么买到这站剩余用完,要么补足当前缺口
ll need = -curFuel;
ll buy = min(need, remain);
totalCost += buy * c;
curFuel += buy;
remain -= buy;
// 如果还有剩余可卖油量,再次推回堆中
if (remain > 0){
pq.push({c, remain});
}
}
// 如果仍然小于0,说明无论如何都买不到足够的油
if (curFuel < 0){
cout << -1 << "\n";
return 0;
}
// 将当前站点信息入堆,后续可能需要在这里买油
pq.push({cost[i], limit[i]});
}
// 如果遍历完,说明可以顺利到达,打印总花费
cout << totalCost << "\n";
return 0;
}
防御力
#include <bits/stdc++.h>
using namespace std;
/*
思路说明:
1. 题目要求每次使用道具后,都会在 A = 2^d 和 B = 3^d 的动态关系中进行更新。
2. 为了尽量提升最终的 d 值,需要根据对 A 和 B 本身的增幅以及对方的间接增幅来排序:
- A 的增加量从小到大排序(cmp1 升序),因为在后面使用大增量时,B 的增长效果会更强。
- B 的增加量从大到小排序(cmp2 降序),因为在前面使用大增量时,A 的增长效果会更强。
3. 然后按照输入字符串顺序 ('0' 表示使用增加 A 值的道具,'1' 表示使用增加 B 值的道具),
从排好序的道具列表中依次选取并输出其原始编号。
*/
#define x first
#define y second
// 最大支持的道具数量
const int N = 1000000 + 10;
int n1, n2;
pair<int,int> a[N], b[N];
string s;
// 对 A 的道具进行升序比较
bool cmp1(const pair<int,int>& u, const pair<int,int>& v) {
if(u.x != v.x) {
return u.x < v.x;
}
return u.y < v.y;
}
// 对 B 的道具进行降序比较
bool cmp2(const pair<int,int>& u, const pair<int,int>& v) {
if(u.x != v.x) {
return u.x > v.x;
}
return u.y < v.y;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n1 >> n2;
// 读入 A 的道具
for(int i = 1; i <= n1; i++) {
cin >> a[i].x;
a[i].y = i; // 保存输入时的原始下标
}
// 读入 B 的道具
for(int i = 1; i <= n2; i++) {
cin >> b[i].x;
b[i].y = i; // 保存输入时的原始下标
}
// A 的增加量从小到大排序
sort(a + 1, a + 1 + n1, cmp1);
// B 的增加量从大到小排序
sort(b + 1, b + 1 + n2, cmp2);
cin >> s;
int p = 1, q = 1; // p, q 分别指向 A、B 道具当前可用位置
// 按照字符串顺序输出对应道具的原始编号
for (auto c: s) {
if (c == '0') {
// 使用增加 A 的道具
cout << "A" << a[p++].second << "\n";
} else {
// 使用增加 B 的道具
cout << "B" << b[q++].second << "\n";
}
}
// 最后一行输出一个大写字母 E
cout << 'E';
return 0;
}
排座椅
#include <bits/stdc++.h>
using namespace std;
/*
思路大要点:
1. 共有 m 行,n 列,可在 1~(m-1) 行之间开通道,1~(n-1) 列之间开通道。
2. 对每个可能的横向通道 i,统计在 (i, i+1) 之间有多少对会被分隔(即这对学生的行坐标分别是 i, i+1)。
同理,对每个可能的纵向通道 j,统计在 (j, j+1) 之间有多少对会被分隔。
3. 选出贡献值最高的 K 条横向通道和 L 条纵向通道,分别从小到大输出。
*/
static const int MAXN = 1010; // 题目 m, n ≤ 1000
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int m, n, k, l, d;
cin >> m >> n >> k >> l >> d;
// rowCuts[i] 表示在行间隙 i 和 i+1 之间开通道,可分隔的交头接耳对数量
// colCuts[j] 表示在列间隙 j 和 j+1 之间开通道,可分隔的交头接耳对数量
int rowCuts[MAXN] = {0}, colCuts[MAXN] = {0};
for (int i = 0; i < d; i++) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
// 如果在行上相邻
if (x1 == x2 + 1 || x1 + 1 == x2) {
// 记录对应行间缝隙
int rowGap = min(x1, x2);
rowCuts[rowGap]++;
}
// 如果在列上相邻
if (y1 == y2 + 1 || y1 + 1 == y2) {
// 记录对应列间缝隙
int colGap = min(y1, y2);
colCuts[colGap]++;
}
}
// 为了选出贡献最高的 K 个行缝隙,需要把 (贡献值, 行缝隙编号) 存起来排序
// 行缝隙可取 1..(m-1),列缝隙可取 1..(n-1)
vector<pair<int,int>> vr, vc;
vr.reserve(m-1);
vc.reserve(n-1);
for (int i = 1; i < m; i++) {
vr.push_back({rowCuts[i], i});
}
for (int j = 1; j < n; j++) {
vc.push_back({colCuts[j], j});
}
// 依贡献值从大到小排列
sort(vr.begin(), vr.end(), [](auto &a, auto &b) {
return a.first > b.first;
});
sort(vc.begin(), vc.end(), [](auto &a, auto &b) {
return a.first > b.first;
});
// 选前 K 条、前 L 条,且输出时要求从小到大输出
// 所以先选出前 K/L,再升序
vector<int> rowAns, colAns;
rowAns.reserve(k);
colAns.reserve(l);
for (int i = 0; i < k; i++) {
rowAns.push_back(vr[i].second);
}
for (int j = 0; j < l; j++) {
colAns.push_back(vc[j].second);
}
sort(rowAns.begin(), rowAns.end());
sort(colAns.begin(), colAns.end());
// 输出
// 第一行输出 K 条横向通道
for (int i = 0; i < k; i++) {
cout << rowAns[i];
if (i < k - 1) cout << " ";
}
cout << "\n";
// 第二行输出 L 条纵向通道
for (int j = 0; j < l; j++) {
cout << colAns[j];
if (j < l - 1) cout << " ";
}
cout << "\n";
return 0;
}
母舰
#include <bits/stdc++.h>
#define x first
#define y second
using namespace std;
using ll = long long;
const ll N = 1e5 + 10;
ll m, n;
ll oth[N], my[N], st[N];
ll ans;
int main()
{
cin >> m >> n;
// 输入敌方防御系统的防御力
for(ll i = 1; i <= m; i++)
{
cin >> oth[i];
}
// 输入我方攻击系统的攻击力
for(ll i = 1; i <= n; i++)
{
cin >> my[i];
}
// 排序,确保从小到大处理
sort(oth + 1, oth + 1 + m);
sort(my + 1, my + 1 + n);
// 处理攻击系统与防御系统的匹配
for(ll i = 1; i <= m; i++)
{
// 找到第一个能打破敌方防御系统的攻击系统
ll x = upper_bound(my + 1, my + 1 + n, oth[i]) - my;
// 如果攻击系统已经被使用过,继续找下一个
while(st[x] && x <= n)
{
x++;
}
// 如果有可用的攻击系统,进行攻击
if(x <= n)
{
st[x] = true; // 标记该攻击系统已被使用
}
}
// 计算剩余攻击系统对母舰的伤害
for(ll i = 1; i <= n; i++)
{
if(!st[i])
{ // 未使用的攻击系统对母舰造成伤害
ans += my[i];
}
}
cout << ans;
return 0;
}
dfs
寒假作业
#include <iostream>
using namespace std;
int a[20]={1,2,3,4,5,6,7,8,9,10,11,12,13};
bool vis[20];
int b[20];
int ans=0;
void dfs(int s,int t)
{
if(s==12)
{
if(b[9]*b[10]==b[11])
{
ans++;
}
}
if(s==3&&b[0]+b[1]!=b[2])//剪枝
{
return;
}
if(s==6&&b[3]+b[4]!=b[5])
{
return;
}
if(s==9&&b[6]+b[7]!=b[8])
{
return;
}
for(int i=0;i<t;i++)
{
if(!vis[i])
{
vis[i]=true;
b[s]=a[i];
dfs(s+1,t);
vis[i]=false;
}
}
}
int main()
{
int n=13;
dfs(0,n); //前n个数的全排列
cout<<ans;
return 0;
}
剪格子
#include <bits/stdc++.h>
using namespace std;
const int N = 15; // 最大格子数,保证足够容纳所有数据
int n, m; // 格子的行数和列数
int a[N][N], vis[N][N]; // a数组存储格子的数值,vis数组用于标记格子是否已被访问
int sum, ans = 100000; // sum为所有格子的和,ans存储最小的分割区域格子数
int d[4][2] = {{1, 0}, {0, -1}, {0, 1}, {-1, 0}}; // 四个方向:下、左、右、上
void dfs(int x, int y, int c, int s)
{
// 剪枝:如果当前区域的和已经超过总和的一半,就没有必要继续搜索
if (2 * s > sum)
{
return;
}
// 如果当前区域的和恰好等于总和的一半,检查是否符合条件
if (2 * s == sum)
{
// 如果找到了一个和为sum/2的区域,记录该区域的格子数
if (c < ans) // 比较当前区域的格子数是否比已有的最小格子数更小
{
ans = c; // 更新最小格子数
}
return;
}
vis[x][y] = 1; // 标记当前格子为已访问
// 遍历四个方向,递归地继续搜索
for (int i = 0; i < 4; i++)
{
int tx = x + d[i][0], ty = y + d[i][1];
// 确保新位置在合法的范围内,且该格子尚未被访问
if (tx >= 1 && tx <= n && ty >= 1 && ty <= m && !vis[tx][ty])
{
// 递归搜索,c+1表示进入下一个格子,s+a[x][y]表示当前区域的和
dfs(tx, ty, c + 1, s + a[x][y]);
}
}
vis[x][y] = 0; // 回溯,标记当前格子为未访问
}
int main()
{
cin >> m >> n;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
cin >> a[i][j];
sum += a[i][j]; // 累加格子值,计算总和
}
}
dfs(1, 1, 0, 0); // 参数解释:从(1, 1)开始,已访问格子数为0,当前和为0
cout << (ans == 100000 ? 0 : ans);
return 0;
}
分考场
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n, m; // n 是人数,m 是认识关系数
bool a[N][N]; // 邻接矩阵,a[i][j] 表示 i 和 j 是否认识
int p[N][N]; // p[i][j] 表示第 i 个考场的第 j 个人
int ans = INT_MAX; // 保存最小考场数量
// 深度优先搜索函数,用于分配考场
void dfs(int id, int classes) {
if (classes >= ans) return; // 如果当前考场数量不小于当前最优解,直接返回
if (id > n) { // 如果所有人都分配完毕
ans = min(ans, classes); // 更新最优解
return;
}
int classnumber, classposition;
// 尝试将第 id 个人分配到已有的考场
for (classnumber = 1; classnumber <= classes; classnumber++) {
classposition = 1;
// 检查该考场中是否有认识的人
while (p[classnumber][classposition] && !a[id][p[classnumber][classposition]]) {
classposition++;
}
if (p[classnumber][classposition] == 0) { // 如果找到空位且没有冲突
p[classnumber][classposition] = id; // 分配到该考场
dfs(id + 1, classes); // 继续分配下一个人
p[classnumber][classposition] = 0; // 回溯,撤销分配
}
}
// 尝试增加一个新的考场
p[classes + 1][1] = id;
dfs(id + 1, classes + 1);
p[classes + 1][1] = 0; // 回溯
}
int main() {
cin >> n >> m;
// 读取认识关系,构建邻接矩阵
for (int i = 1; i <= m; i++) {
int b, c;
cin >> b >> c;
a[b][c] = true;
a[c][b] = true;
}
dfs(1, 1); // 从第一个人开始,初始考场数量为 1
cout << ans; // 输出最小考场数量
return 0;
}
像素放置
#include <iostream>
#include <cmath>
#include <algorithm>
#include <map>
#include <vector>
#include <cstring>
#include <set>
#include <queue>
typedef long long ll;
using namespace std;
int n, m;
/*
v[i][j] 用来存储棋盘中每个格子的数字信息
如果 v[i][j] == -1,表示该格子没有数字限制
否则表示以 (i, j) 为中心的 3x3 区域中需要被涂成黑色的格子数
*/
int v[15][15];
/*
ans[i][j] 表示棋盘中每个格子的涂色结果
0 表示涂成白色,1 表示涂成黑色
*/
int ans[15][15];
/*
get 函数统计以 (i, j) 为中心的 3×3 区域中,被涂成黑色(1)的格子数量
*/
int get(int i, int j) {
int cnt = 0;
for (int dx = -1; dx <= 1; dx++) {
for (int dy = -1; dy <= 1; dy++) {
int xx = dx + i, yy = dy + j;
// 如果该位置被涂为黑色(ans[xx][yy] == 1),则计数加 1
cnt += (ans[xx][yy] == 1);
}
}
return cnt;
}
/*
check 函数判断 (i, j) 格子是否满足要求:
- 如果 v[i][j] == -1,表示该格子没有数字要求,直接返回 true
- 否则比较 get(i, j) 的结果与 v[i][j] 是否相等
*/
bool check(int i, int j) {
if (v[i][j] == -1) return true;
return (get(i, j) == v[i][j]);
}
/*
dfs 函数使用回溯算法对整张棋盘进行填色
(i, j) 表示当前要处理的行和列
*/
void dfs(int i, int j) {
// 当 i 超过 n,说明所有格子都已经尝试填色
if (i == n + 1) {
// 在这里检查倒数两行的格子是否都满足 check 条件
for (int xx = max(n - 1, 1); xx <= n; xx++) {
for (int yy = 1; yy <= m; yy++) {
if (!check(xx, yy)) return; // 若有不满足,则返回
}
}
// 若最后两行也满足条件,输出答案并结束程序
for (int xx = 1; xx <= n; xx++) {
for (int yy = 1; yy <= m; yy++) {
cout << ans[xx][yy];
}
cout << '\n';
}
exit(0);
}
// 如果 j 已经到达最后一列,则处理该列后,进入下一行
if (j == m) {
// 尝试将当前格子涂成白色
ans[i][j] = 0;
// 剪枝判断:如果已知上方或斜上方不满足条件,则不再递归
if (!(i > 1 && (check(i - 1, j) == 0 || (j > 1 && check(i - 1, j - 1) == 0))))
dfs(i + 1, 1);
// 尝试将当前格子涂成黑色
ans[i][j] = 1;
if (!(i > 1 && (check(i - 1, j) == 0 || (j > 1 && check(i - 1, j - 1) == 0))))
dfs(i + 1, 1);
return;
}
// 尝试将当前格子涂成白色
ans[i][j] = 0;
// 剪枝:若上方某些地方不满足条件,则无需继续
if (!(i > 1 && j > 1 && check(i - 1, j - 1) == 0))
dfs(i, j + 1);
// 尝试将当前格子涂成黑色
ans[i][j] = 1;
if (!(i > 1 && j > 1 && check(i - 1, j - 1) == 0))
dfs(i, j + 1);
}
/*
solve 函数读取输入,并用 dfs(1, 1) 开始搜索解
*/
void solve() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
string temp;
cin >> temp;
for (int j = 1; j <= m; j++) {
// 如果当前位置为 '_',表示没有数字限制
if (temp[j - 1] == '_') {
v[i][j] = -1;
} else {
// 否则转换成该数字对应的整数并存储
v[i][j] = (temp[j - 1] - '0');
}
}
}
// 从 (1, 1) 开始对棋盘进行深度优先搜索填色
dfs(1, 1);
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
solve();
}
迷宫(2017)
- 迷宫为10×10网格,每个格子里有一个方向字母(L/R/U/D)。
- 对每个初始位置( i , j ),利用DFS模拟移动路径:
- 若移动到网格边界外(下标越界),则说明该路径可以走出迷宫,对应玩家记为已成功走出。
- 若在移动过程中再次进入已访问过的格子,说明存在循环,对应玩家无法走出迷宫。
- 统计所有初始格子对应的DFS中能走出迷宫的路径个数,即为题意所需的答案。
方格分割
传送门
根据分割线递归,同时标注两个点,递归的的点和对称的点,因为旋转也算一种方案,所以得到的ans需要除4
#include <bits/stdc++.h>
using namespace std;
// d 数组存储四个方向的移动向量:上、下、左、右
int d[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}};
// st[x][y] 用来标记点 (x, y) 是否已被访问
bool st[10][10];
// ans 用来记录有效的剪切方式总数
int ans;
/*
dfs 函数用于从 (x, y) 出发,进行深度优先搜索,寻找从中心 (3, 3) 到达网格外层的对称路径。
当到了外层 (x == 0 || x == 6 || y == 0 || y == 6) 时,即找到了一条有效剪切路径,ans 加一。
*/
void dfs(int x, int y)
{
// 如果到达边界,表示找到了一条从中心到边界的对称路径,ans 加一并返回
if (x == 0 || x == 6 || y == 0 || y == 6)
{
ans++;
return;
}
// 先将当前点 (x, y) 和与之镜面对称的点 (6 - x, 6 - y) 标记为已访问
st[x][y] = true;
st[6 - x][6 - y] = true;
// 分割线尝试向四个方向移动
for(int i = 0; i < 4; i++)
{
int dx = x + d[i][0], dy = y + d[i][1];
// 如果超出范围,跳过
if(dx < 0 || dx > 6 || dy < 0 || dy > 6)
{
continue;
}
// 如果下一个点未被访问过,则继续 DFS
if(!st[dx][dy])
{
dfs(dx, dy);
}
}
// 回溯:将当前点及对称点恢复为未访问状态
st[x][y] = false;
st[6 - x][6 - y] = false;
}
int main()
{
// 以左下角为原点(0,0),从网格中心 (3, 3) 开始搜索
dfs(3, 3);
// 因为每种路径因为旋转可能被重复多次统计,所以最后除以 4
cout << ans / 4 << endl;
return 0;
}
全球变暖
#include <iostream>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
using namespace std;
// 全局变量:
bool flag;// flag 用来标记当前岛屿是否确定不会被完全淹没
char a[1010][1010];// 地图数组 a,用来存储 N×N 的输入字符('.' 表示海洋,'#' 表示陆地)
int cnt = 0;// cnt 用来统计当前搜索像素周围 '#' 的个数
int n;// n 表示地图的大小
int d[4][2] = { {1,0}, {-1,0}, {0,1}, {0,-1} };// d 为方向数组,用于在 DFS 中向上下左右移动
int ans = 0;// ans 统计“不会完全被淹没”的岛屿个数
int res_ans = 0;// res_ans 统计总的岛屿个数
/*
dfs(x, y):
- 如果 (x, y) 不在地图内,或当前不是陆地 '#', 则直接返回。
- 若 flag 仍为 false,说明尚未确定该岛屿是否可以幸存,需要检测当前像素 (x, y) 四周是否全是 '#',从而判定此岛屿是否存在“不会淹没”点。
- 遍历结束后,将 (x, y) 用 '*' 标记,表示已访问,防止重复搜索。
*/
void dfs(int x,int y)
{
// 越界或者不是陆地则直接返回
if(x < 0 || x >= n || y < 0 || y >= n) return;
if(a[x][y] != '#') return;
// 如果目前还没有确定会幸存
if(flag == false)
{
// cnt 用来统计周围四个点中的 '#' 数量
cnt = 0;
for(int i = 0; i < 4; i++)
{
int tx = x + d[i][0];
int ty = y + d[i][1];
// 如果在范围内且不为 '.', 则算作陆地
if(tx >= 0 && tx < n && ty >= 0 && ty < n && a[tx][ty] != '.')
{
cnt++;
}
}
// 若四周都是陆地,则该像素不会被淹没
// 因此这个岛屿整座都不会被完全淹没
if(cnt == 4)
{
ans++; // “不会被淹没”的岛屿数 +1
flag = true; // 将标识置为 true,后续不再需要判断
}
}
// 将该像素设为 '*',标记为已访问
a[x][y] = '*';
// DFS 深度搜索上下左右四个方向
for(int i = 0; i < 4; i++)
{
int tx = x + d[i][0];
int ty = y + d[i][1];
dfs(tx, ty);
}
}
int main()
{
cin >> n;
// 读入地图,每行一个字符串
for(int i = 0; i < n; i++)
{
scanf("%s", a[i]);
}
// 遍历地图
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
// 如果找到未被标记的陆地 '#',说明发现一座新的岛屿
if(a[i][j] == '#')
{
res_ans++; // 岛屿总数 +1
flag = false; // 初始默认为会被淹没
dfs(i, j); // DFS 标记整座岛屿
}
}
}
// 输出:能找到至少一个“不会被淹没”像素的岛屿数为 ans
// 因此“会被完全淹没”的岛屿数 = res_ans - ans
cout << res_ans - ans << endl;
return 0;
}
分糖果
#include <bits/stdc++.h>
using namespace std;
int ans;
void dfs(int n1,int n2,int u)//n1为第一种糖果数,n2为第二种糖果数,u为当前枚举的第u个人
{
if(u==7)
{
if(n1+n2>=2&&n1+n2<=5)
{
ans++;
}
return;
}
for(int i=2;i<=5;i++)//第u个人分配到的糖果数
{
for(int j=0;j<=min(i,n1);j++)//分到的第一种糖果
{
int k=i-j;//分到的第二种糖果
if(k>n2)continue;
dfs(n1-j,n2-k,u+1);
}
}
}
int main()
{
// int a,b;
// cin>>a>>b;
// dfs(a,b,1);
// cout<<ans;
cout<<5067671;
return 0;
}
玩具蛇
#include <iostream>
using namespace std;
int ans; // 记录方案数
// 四个方向:上、下、左、右
int d[][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
// st 数组表示 4x4 方格中每个格子的占用情况
bool st[5][5];
// 深度优先搜索(DFS)函数
void dfs(int x, int y, int len) {
// 如果当前坐标超出了边界,返回
if (x < 0 || x >= 4 || y < 0 || y >= 4) {
return;
}
// 如果当前格子已经被占用,返回
if (st[x][y] == true) {
return;
}
// 如果玩具蛇已经放置 16 节,方案数加 1
if (len == 16) {
ans++;
return;
}
// 标记当前格子为已占用
st[x][y] = true;
// 尝试四个方向继续放置下一节
for (int i = 0; i < 4; i++) {
int dx = x + d[i][0], dy = y + d[i][1];
// 判断下一个格子是否在有效范围内
if (dx >= 0 && dx < 4 && dy >= 0 && dy < 4) {
// 递归继续放置
dfs(dx, dy, len + 1);
}
}
// 回溯,将当前格子标记为未占用
st[x][y] = false;
}
int main() {
// 遍历 4x4 方格中的每个位置作为起始点
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
// 从每个位置开始放置玩具蛇,起始长度为 1
dfs(i, j, 1);
}
}
// 输出方案数
cout << ans;
return 0;
}
四阶幻方
/*
思路说明:
1. 将 1 至 16 这 16 个数字填入一个 4×4 的方格(用一维数组 mp[1..16] 表示),
并保证每个数字只出现一次(通过 used 数组标记)。
2. 固定左上角为 1,即 mp[1] = 1,使得搜索空间缩小。
3. 搜索时,按顺序为 pos=1 到 pos=16 的格子依次挑选数字。
- 当 pos=5 时,表示行 1 (mp[1], mp[2], mp[3], mp[4]) 已填完,检验它们的和是否为 34。
- 当 pos=9 时,表示行 2 已填完,检查 mp[5]+mp[6]+mp[7]+mp[8] == 34。
- 当 pos=13 时,表示行 3 已填完,检查 mp[9]+mp[10]+mp[11]+mp[12] == 34。
- 当 pos=14、15、16 时,分别检查部分列和对角线(如第二列、第三列、斜对角等),不满足即回溯。
- 最后在 pos=17 时,表示全部填完,这时检查主对角线 mp[1]+mp[6]+mp[11]+mp[16] == 34。
4. 若所有行、列及两条对角线都满足值为 34 的魔方阵要求,则计数器 ans 自增。
*/
#include <bits/stdc++.h>
using namespace std;
bool used[20]; // 记录数字 1..16 是否已被使用
int mp[20]; // 一维表示 4×4 方格,每个位置存放一个唯一的数字
int ans; // 计数器,记录满足条件的幻方数
void dfs(int pos)
{
// 当 pos=17 时说明已经为 mp[1..16] 全部分配完数字
if(pos == 17)
{
// 检查主对角线和是否为 34
if(mp[1] + mp[6] + mp[11] + mp[16] == 34)
ans++;
return;
}
// pos=5 时,检查第一行是否为 34
else if(pos == 5)
{
if(mp[1] + mp[2] + mp[3] + mp[4] != 34)
return;
}
// pos=9 时,检查第二行是否为 34
else if(pos == 9)
{
if(mp[5] + mp[6] + mp[7] + mp[8] != 34)
return;
}
// pos=13 时,检查第三行是否为 34
else if(pos == 13)
{
if(mp[9] + mp[10] + mp[11] + mp[12] != 34)
return;
}
// pos=14 时,检查一条斜线和第一列是否为 34
else if(pos == 14)
{
// 另一条对角线:mp[4] + mp[7] + mp[10] + mp[13]
// 第 1 列 :mp[1] + mp[5] + mp[9] + mp[13]
if((mp[4] + mp[7] + mp[10] + mp[13] != 34) ||
(mp[1] + mp[5] + mp[9] + mp[13] != 34))
return;
}
// pos=15 时,检查第 2 列是否为 34
else if(pos == 15)
{
if(mp[2] + mp[6] + mp[10] + mp[14] != 34)
return;
}
// pos=16 时,检查第 3 列是否为 34
else if(pos == 16)
{
if(mp[3] + mp[7] + mp[11] + mp[15] != 34)
return;
}
// 依次尝试给 mp[pos] 赋值 1..16,若数字未被用,则递归搜索
for(int i = 1; i <= 16; i++)
{
if(!used[i])
{
mp[pos] = i;
used[i] = true;
dfs(pos + 1);
used[i] = false; // 回溯
}
}
}
int main()
{
// 固定左上角格子 mp[1] = 1
// 标记数字 1 已被使用
mp[1] = 1;
used[1] = true;
// 从 mp[2] 开始递归搜索
dfs(2);
// 输出满足条件的幻方总数
cout << ans << endl;
return 0;
}
买瓜
#include <bits/stdc++.h>
using namespace std;
static const int N = 35;
int n, m;
long long a[N]; // 存瓜的重量
long long sumArr[N]; // 后缀和数组
long long ans = LLONG_MAX;
// dfs(u, s, cnt): 当前考虑第 u 个瓜 (1-based),当前重量和为 s,已经劈了 cnt 个瓜
void dfs(int u, long long s, long long cnt)
{
// 一旦恰好凑到 m,更新最优解并返回
if (s == m)
{
ans = min(ans, cnt);
return;
}
// 剪枝:如果已劈的次数 >= ans,或 u > n (超出瓜的数量),或已经无法凑到 m,直接返回
if (cnt >= ans || u > n || s > m || s + sumArr[u] < m)
return;
// 1. 不买第 u 个瓜
dfs(u + 1, s, cnt);
// 2. 劈第 u 个瓜,买一半
dfs(u + 1, s + a[u] / 2, cnt + 1);
// 3. 买整条瓜
dfs(u + 1, s + a[u], cnt);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
// 将目标重量 m 左移一位 (×2),配合瓜的重量也 ×2,便于劈瓜时使用整数计算
m <<= 1;
for (int i = 1; i <= n; i++)
{
cin >> a[i];
// 每个瓜的重量也 ×2
a[i] <<= 1;
}
// 从大到小排序,放在前面的瓜更大,能更快凑够重量
sort(a + 1, a + n + 1, greater<long long>());
// 计算后缀和:sumArr[u] 表示从第 u 个瓜到最后的总重量
// 帮助做“如果剩下瓜的总和也凑不到 m,就提前剪枝”
for (int i = n; i >= 1; i--)
{
sumArr[i] = sumArr[i + 1] + a[i];
}
// 从第 1 个瓜开始,当前重量和为 0,尚未劈过瓜
dfs(1, 0, 0);
// 如果 ans 还保持初始,则说明无法凑到目标重量
if (ans == LLONG_MAX)
cout << -1 << "\n";
else
cout << ans << "\n";
return 0;
}
与或异或
//c++
#include <bits/stdc++.h>
using namespace std;
int sum = 0;
int ops[11]; // 共10个操作符
int arr[5][6]; //arr[i][j]表示第i层第j个值
int op[5][5]; //op[i][j]表示第i层第j个操作符
void dfs(int cnt) {
//cnt为11的时候,表示前10个操作符全部枚举
if (cnt == 11) {
for (int i = 1; i <= 4; i++) op[1][i] = ops[i]; //赋值第一层操作符
for (int i = 1; i <= 3; i++) op[2][i] = ops[i + 4]; //赋值第二层操作符,前四个是第一层的
for (int i = 1; i <= 2; i++) op[3][i] = ops[i + 7]; //同上,前七个是前两层的
for (int i = 1; i <= 1; i++) op[4][i] = ops[i + 9]; //同上
//按照题意进行计算
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4 - i + 1; j++) {
if (op[i][j] == 0) arr[i][j] = arr[i - 1][j] | arr[i - 1][j + 1];
if (op[i][j] == 1) arr[i][j] = arr[i - 1][j] ^ arr[i - 1][j + 1];
if (op[i][j] == 2) arr[i][j] = arr[i - 1][j] & arr[i - 1][j + 1];
}
}
if (arr[4][1] == 1)
sum++;
return;
}
//共有三种操作符号, 0、1、2 分别代表 |、^、&
//枚举第cnt个操作符,假设为 0
ops[cnt] = 0;
//开始枚举下一个操作符
dfs(cnt + 1);
//枚举第cnt个操作符,假设为 1
ops[cnt] = 1;
dfs(cnt + 1);
//枚举第cnt个操作符,假设为 2
ops[cnt] = 2;
dfs(cnt + 1);
}
int main() {
//初始化第一层的电位
arr[0][1] = 1;
arr[0][2] = 0;
arr[0][3] = 1;
arr[0][4] = 0;
arr[0][5] = 1;
//从第枚举每一个运算符开始
dfs(1);
cout << sum << endl;
return 0;
}
路径之谜
#include<iostream>
using namespace std;
#define N 404
int paths[N];//记录路径
int dx[4]={1,0,-1,0};//控制走的方向
int dy[4]={0,1,0,-1};
int cntx[N],cnty[N];//记录两个方向上每个箭靶中箭的数量
int n,tot=0;
bool success=false;//是否到达终点
bool visited[N][N];//标记某点是否走过
bool check() {
for (int i = 1; i <= n; ++i) {
if(cntx[i]!=0 || cnty[i] != 0)
return false;
}
return true;
}
void dfs(int x, int y, int num)//num记录步数
{
paths[num]=(y-1)*n+x-1; //将该点编号记录到路径中
visited[x][y]=true;//将该点标记为已经走过的状态
cntx[x]--;//拔掉对应的箭
cnty[y]--;
if(/*num==tot/2&&*/x==n&&y==n&&check())//判断是否到达终点
{
success=true;
return;
}
for(int i=0;i<4;i++)//上下左右四个方向搜索下一步
{
int xx=x+dx[i],yy=y+dy[i];
if(!success&&!visited[xx][yy]&&1<=xx&&xx<=n&&yy>=1&&yy<=n)//没有到达终点,下一步(xx,yy)未走过且在地图范围内
{
if(cntx[xx]>0&&cnty[yy]>0)//该点对应箭靶上有箭,说明该点可以走
{
dfs(xx,yy,num+1);//搜索下一步
visited[xx][yy]=false;//复原,回溯
cntx[xx]++;
cnty[yy]++;
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&cntx[i]);
tot+=cntx[i];//tot 统计箭的总数量
}
for(int i=1;i<=n;i++)
{
scanf("%d",&cnty[i]);
tot+=cnty[i];
}
dfs(1,1,1);
for(int i=1;i<=tot/2;i++)//输出答案。
{
printf("%d ",paths[i]);
}
return 0;
}
走迷宫(打印路径)
走迷宫
本题其实并不难,但我实际上交了第2遍才过,下面列出本题的大坑。
1.如果没有解要输出-1。
2.搜索前进的方向要遵循左上右下的优先顺序。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=1005;
vector<pair<int,int>> path;
int m,n;
bool st[N][N];
int mp[N][N];
int startx,starty,endx,endy;
int d[4][2]={{0,-1},{-1,0},{0,1},{1,0}};
bool success;
void dfs(int x,int y)
{
if(x<1||x>m||y<1||y>n)
{
return;
}
if(x==endx&&y==endy)
{
success=true;
for (const auto& p : path)
{
cout << "(" << p.first << "," << p.second << ")";
if (&p != &path.back()) cout << "->";
}
cout << endl;
}
for(int i=0;i<4;i++)
{
int dx=x+d[i][0],dy=y+d[i][1];
if(1<=dx&&dx<=m&&1<=dy&&dy<=n&&!st[dx][dy]&&mp[dx][dy]==1)
{
path.push_back({dx,dy});
st[dx][dy]=true;
dfs(dx,dy);
path.pop_back();
st[dx][dy]=false;
}
}
}
int main()
{
cin>>m>>n;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
cin>>mp[i][j];
}
}
cin>>startx>>starty>>endx>>endy;
st[startx][starty]=true;
path.push_back({startx,starty});
dfs(startx,starty);
if(success==false)
{
cout<<-1<<endl;
}
return 0;
}
数的划分
#include <bits/stdc++.h>
using namespace std;
const int N=1005;
/*
题意:给定整数 n 与 k,将 n 分成 k 份(每份都是正整数),
并且不考虑顺序的不同(如 1,1,5 和 1,5,1 等视为相同的分法),
问共有多少种不同的分法。
核心思路:使用带剪枝的 DFS(或称回溯)进行搜索。
其中 dfs(num, part, now) 表示将 “剩余待分的数 num”
分成 “part 份”,本次选出的最小数不能小于 now,
保证不产生重复(如 1,2 和 2,1)。
具体流程:
1. 如果仅剩最后 1 份 (part==1),则这部分只能全部拿走,
就有 1 种分法。
2. 否则,从当前最小可选值 now 枚举到 num / part,
将其中的数 i 选为当前份,然后对剩余的 (num - i)
进行 (part-1) 份的搜索,继续累加到 sum。
3. 通过不往上枚举到 num 而只到 (num / part),
实现了剪枝并防止重复枚举。
*/
int n,k;
// dfs(num, part, now):
// num 表示当前还剩下多少数未分
// part 表示还需要分几份
// now 表示此时要分出去的每份起始值(防止重复)
int dfs(int num,int part,int now)
{
if(part==1)
{
return 1;//当 n=1 时,仅有一种分法。
}
int sum=0;
// 从当前最小值 now 到 num/part 枚举
// 只到 num/part,是为了防止后面的份数无法分配
for(int i=now;i<=num/part;i++)
{
// 这里将 i 作为当前这一份,然后对剩下 (num - i) 进行搜索
sum+=dfs(num-i,part-1,i);
}
return sum;
}
int main()
{
cin>>n>>k;
cout<<dfs(n,k,1)<<endl;
return 0;
}
迷宫(合法路径数)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N = 1005;
vector<pair<int,int>> path;
// 迷宫的长宽和障碍数量
int m, n, t;
// 记录某个格子是否被访问过
bool st[N][N];
// 迷宫的矩阵,0表示障碍,1表示可走的路
int mp[N][N];
// 起点坐标和终点坐标
int startx, starty, endx, endy;
// 用于表示上下左右四个方向的数组
int d[4][2] = {{0,-1}, {-1,0}, {0,1}, {1,0}};
// 存储答案,记录路径的数量
int ans;
// 深度优先搜索函数
void dfs(int x, int y)
{
// 如果当前位置超出迷宫边界,返回
if (x < 1 || x > m || y < 1 || y > n)
{
return;
}
// 如果到达终点,路径数加一
if (x == endx && y == endy)
{
ans++;
}
// 遍历四个方向进行递归搜索
for (int i = 0; i < 4; i++)
{
int dx = x + d[i][0], dy = y + d[i][1];
// 判断新位置是否有效:在迷宫内、没有障碍、没有访问过
if (1 <= dx && dx <= m && 1 <= dy && dy <= n && !st[dx][dy] && mp[dx][dy] == 1)
{
// 标记当前位置为已访问
st[dx][dy] = true;
// 递归搜索新位置
dfs(dx, dy);
// 回溯,撤销标记
st[dx][dy] = false;
}
}
}
int main()
{
// 输入迷宫的大小和障碍数量
cin >> m >> n >> t;
// 输入起点和终点的坐标
cin >> startx >> starty >> endx >> endy;
// 初始化迷宫,所有格子初始状态为可通行
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
mp[i][j] = 1; // 1表示可通过
}
}
// 输入障碍位置,将障碍位置标记为0,表示不可通过
for (int i = 1; i <= t; i++)
{
int x, y;
cin >> x >> y;
mp[x][y] = 0; // 0表示障碍
}
// 标记起点已访问,避免从起点重新开始
st[startx][starty] = true;
// 从起点开始进行深度优先搜索
dfs(startx, starty);
// 输出最终的路径数
cout << ans;
return 0;
}
健康的荷斯坦奶牛
#include <bits/stdc++.h> // 包含所有标准库
using namespace std;
const int N = 30; // 维他命种类的最大数量
int n; // 维他命种类数
int need[N]; // 存储奶牛每天所需的每种维他命的最小量
int m; // 饲料种类数
int b[1000][1000]; // 每种饲料包含的各种维他命的量
int select_[1000]; // 当前选择的饲料编号
int minn = 100000000; // 最小饲料种类数,初始化为一个很大的数
int ans[N]; // 存储最终选择的饲料的编号
// 判断当前选择的饲料能否满足所有维他命的需求
bool pd(int x) {
for (int i = 1; i <= n; i++) { // 遍历每种维他命
int sum = 0; // 某种维他命的总量
for (int j = 1; j <= x; j++) { // 累加选择的饲料中的维他命量
sum += b[select_[j]][i];
}
// 如果某种维他命的总量不足,返回 false
if (sum < need[i]) return false;
}
return true; // 所有维他命满足需求,返回 true
}
// 递归搜索满足条件的饲料组合
void dfs(int t, int s) {
if (t > m) { // 所有饲料考虑完毕
if (pd(s)) { // 检查当前选定的饲料组合是否满足需求
if (s < minn) { // 如果当前选择的饲料数量小于已知的最优解
minn = s; // 更新最小饲料种类数
for (int i = 1; i <= minn; i++) {
ans[i] = select_[i]; // 更新答案数组
}
}
}
return; // 返回
}
// 尝试将当前饲料添加到选择中
select_[s + 1] = t;
dfs(t + 1, s + 1); // 递归选择下一个饲料
select_[s + 1] = 0; // 回溯,清空当前选择
// 尝试不将当前饲料加入选择
dfs(t + 1, s); // 递归到下一个饲料
}
int main() {
cin >> n; // 输入维他命种类数
for (int i = 1; i <= n; i++) {
cin >> need[i]; // 输入每种维他命的最小需求量
}
cin >> m; // 输入饲料种类数
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
cin >> b[i][j]; // 输入每种饲料的维他命组成
}
}
// 开始搜索,从第一种饲料和0个选择的饲料开始
dfs(1, 0);
cout << minn << ' '; // 输出最小饲料种类数
for (int i = 1; i <= minn; i++) {
cout << ans[i] << ' '; // 输出所选择的饲料编号
}
return 0; // 程序结束
}
bfs
迷宫
#include <bits/stdc++.h>
using namespace std;
// 定义结构体node来存储每个节点的信息
struct node {
int x; // 当前节点的横坐标
int y; // 当前节点的纵坐标
string path; // 记录从起点到当前节点的路径
};
// 定义迷宫的地图,最大大小为30x50
char mp[31][51];
// 定义四个方向的字符,分别代表下、左、右、上的移动
char k[4] = {'D', 'L', 'R', 'U'};
// 定义四个方向的移动方式(下、左、右、上)
int d[4][2] = {{1, 0}, {0, -1}, {0, 1}, {-1, 0}};
// 用于标记每个位置是否已被访问,1表示已访问,0表示未访问
int vis[30][50];
// 广度优先搜索函数
void bfs() {
// 定义起点
node start;
start.x = 0;
start.y = 0;
start.path = ""; // 起点路径为空
vis[0][0] = 1; // 标记起点为已访问
queue<node> q; // 创建队列用于BFS
q.push(start); // 将起点入队
// 当队列不为空时,进行BFS
while (!q.empty()) {
node now = q.front(); // 取出队首元素
q.pop(); // 弹出队首
// 如果当前节点是终点(29, 49),打印路径并返回
if (now.x == 29 && now.y == 49) {
cout << now.path; // 输出从起点到终点的路径
return;
}
// 遍历四个方向,扩展邻居节点
node next;
for (int i = 0; i < 4; i++) {
// 计算下一步的位置
next.x = now.x + d[i][0];
next.y = now.y + d[i][1];
// 判断下一步是否在地图范围内,且该位置为可通行('0')并且未被访问过
if (0 <= next.x && next.x <= 29 && 0 <= next.y && next.y <= 49 &&
mp[next.x][next.y] == '0' && vis[next.x][next.y] == 0) {
vis[next.x][next.y] = 1; // 标记该位置为已访问
next.path = now.path + k[i]; // 更新路径,附加当前移动方向
q.push(next); // 将新的节点入队
}
}
}
}
int main() {
// 输入地图数据
for (int i = 0; i < 30; i++) {
cin >> mp[i];
}
// 调用bfs函数执行广度优先搜索
bfs();
return 0;
}
九宫重排
#include <bits/stdc++.h>
using namespace std;
/*
题意:给定一个九宫格初态和终态(包含 1~8 一共 8 个数字以及 1 个空格“.”),
每次只能将和空格相邻(上下左右)的数字移动到空格位置。
问从初态到终态最少需要多少步?
思路:使用 BFS(宽度优先搜索)框架进行求解。
1. 将九宫的字符串表示(如 "12345678.")作为节点。
2. 每个节点可进行的操作:将空位 '.' 与其相邻格中的数字交换,生成新状态。
3. 在 BFS 中,每个取出的当前状态,尝试所有可能的邻居状态(四个方向),
若没被访问过,就加入队列,并记录步数。
4. 如果在搜索过程中到达终态,则返回此时的步数。
5. 若搜索结束仍未找到终态,返回 -1。
关键点:
- 用 map<string,int> 来保存状态对应的最短步数,避免重复搜索。
- “空位” 在字符串的索引为 k,九宫行列对应 x = k/3, y = k%3,通过邻接数组 dx, dy 得到可移动的新位置。
- BFS 保证首次到达终态即为最短步数。
*/
int dx[] = {-1, 0, 1, 0}; // 上、右、下、左 行增量
int dy[] = { 0, 1, 0, -1}; // 上、右、下、左 列增量
// mp 用来记录已访问状态以及对应的最短步数
map<string, int> mp;
// bfs 函数:传入初始状态 s1 和目标状态 s2,返回最短步数,若无解则返回 -1
int bfs(string s1, string s2)
{
// 队列初始化,存储待搜索状态
queue<string> q;
q.push(s1);
// 初始状态步数置为 0
mp[s1] = 0;
// 开始 BFS
while(!q.empty())
{
// 当前状态出队
string ss = q.front();
q.pop();
// dist 为当前状态到 s1 的步数
int dist = mp[ss];
// 如果已到达目标状态 s2,则立即返回所需步数
if(ss == s2)
{
return dist;
}
// 找到空格 '.' 在 ss 中的位置
int k = ss.find('.');
// 计算空格所对应的行、列
int x = k / 3;
int y = k % 3;
// 尝试对空格上下左右四个方向的数字进行移动
for(int i = 0; i < 4; i++)
{
int xx = x + dx[i];
int yy = y + dy[i];
// 判断移动后是否还在九宫格 3x3 范围内
if(0 <= xx && xx < 3 && 0 <= yy && yy < 3)
{
// 复制当前状态
string tmp = ss;
// 和空格位置交换
swap(tmp[k], tmp[xx * 3 + yy]);
// 如果新状态还未访问,则加入队列并记录步数
if(mp.count(tmp) == 0)
{
mp[tmp] = dist + 1;
q.push(tmp);
}
}
}
}
// 如果 BFS 搜索完仍无法到达终态,返回 -1
return -1;
}
int main()
{
string s1, s2;
// 读取初态和终态
cin >> s1 >> s2;
// 调用 bfs,输出结果
cout << bfs(s1, s2);
return 0;
}
迷宫
#include <bits/stdc++.h>
using namespace std;
/*
整体思路:
1. 由于游戏中可以从终点 (n, n) 出发,向所有其他格子反向 BFS(或称反向层序搜索),
我们可以计算得到每个格子到终点的最短距离。
2. BFS 的过程中,每前进一步可以是上下左右移动,也可以通过传送门移动,花费同样的步数 1。
3. 在 BFS 完成后,dist[i][j] 存储了格子 (i, j) 到终点 (n, n) 的最短步数。
4. 最后把所有格子的最短步数相加再除以总格子数 n*n,即为期望步数。
下面针对关键部分做额外注释。
*/
typedef pair<int, int> PII;
const int N = 2005;
int n, m;
// vis 用于记录在 BFS 中是否已经访问过
bool vis[N][N];
// is_door[x][y] 用于标识 (x, y) 是否与某个传送门关联
bool is_door[N][N];
// door[x][y] 存储了与格子 (x, y) 通过传送门直接相连的其他格子坐标
vector<PII> door[N][N];
int dist[N][N];
// 用于向四个方向移动时的坐标偏移量
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
void bfs() {
// dist数组初始赋值为 INT_MAX,表示一开始距离未知
for (int i = 0; i < N; i++) {
for (int j = 0; j < N; j++) {
dist[i][j] = INT_MAX;
}
}
// BFS 队列
queue<PII> q;
// 终点 (n, n) 到自身的距离为 0
dist[n][n] = 0;
q.push({n, n});
// 起始时将 (n, n) 标记为已访问
vis[n][n] = true;
while (!q.empty()) {
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
// 若当前格子有传送门,则尝试使用传送门到达相连格子
if (is_door[x][y]) {
for (auto &s : door[x][y]) {
// 若使用传送门可以更新最短距离,就将该相邻格子入队
if (dist[s.first][s.second] > dist[x][y] + 1) {
dist[s.first][s.second] = dist[x][y] + 1;
q.push(s);
}
}
}
// 尝试向四个方向走一步
for (int i = 0; i < 4; i++) {
int xx = x + dx[i], yy = y + dy[i];
// 合法性判断:坐标需在 [1, n] 范围内,且没有访问过该格子
if (1 <= xx && xx <= n && 1 <= yy && yy <= n && !vis[xx][yy]) {
// 若可以改写最短距离,则更新并入队
if (dist[xx][yy] > dist[x][y] + 1) {
dist[xx][yy] = dist[x][y] + 1;
q.push({xx, yy});
vis[xx][yy] = true;
}
}
}
}
}
int main() {
cin >> n >> m;
// 读取传送门信息,并将对应的格子标记为有门、并存储相连接的另一端坐标
for (int i = 1; i <= m; i++) {
int x1, y1, x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
door[x1][y1].push_back({x2, y2});
door[x2][y2].push_back({x1, y1});
is_door[x1][y1] = true;
is_door[x2][y2] = true;
}
// 执行 BFS,求出从终点到每个格子的最短步数(反向)
bfs();
long long sum = 0;
// 将所有格子的 dist 之和求出
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
sum += dist[i][j];
}
}
// 期望值 = 所有格子到终点距离的平均数
printf("%.2lf\n", (double)sum / (n * n));
return 0;
}
跳蚱蜢
#include <iostream>
#include<queue>
#include<unordered_map>
#include<algorithm>
using namespace std;
queue<string> q;
unordered_map<string, int> d;
int mv[] = {1, 2, -1, -2};//偏移量
int bfs() {
string end = "087654321";//目标
string start = "012345678";//初始状态
q.push(start);
d[start] = 0;
while (!q.empty()) {
auto t = q.front();
q.pop();
if (t == end) return d[t];//如果找到目标序列,输出交换次数
int dis = d[t];
int x = t.find('0');//空盘子的位置
for (int i = 0; i < 4; i++) {
int y = (x + mv[i] + 9) % 9;//可交换的位置
swap(t[x], t[y]);
if (!d.count(t)) {//防止重复
q.push(t);
d[t] = dis + 1;
}
swap(t[x], t[y]);//对于一种排列,每次交换都独立。撤销之前的交换操作。
}
}
return -1;
}
int main() {
cout << bfs() << endl;
return 0;
}
大胖子走迷宫
#include <bits/stdc++.h>
using namespace std;
/*
整体思路:
1. 使用 BFS (队列) 从起点 (3, 3) 开始,一方面可以原地等待(时间 +1),另一方面向四个方向尝试移动。
2. 小明根据经过的时间 t,不同体型对应占用 5×5、3×3 或 1×1 的空间:
- 当 t < k:占用 5×5,在代码里用 m=2 表示半径为2。
- 当 k <= t < 2k:占用 3×3,m=1。
- 当 t >= 2k:占用 1×1,m=0。
3. 函数 check(x,y,nowtime) 用于判断坐标 (x,y) 在当前时间能不能容纳小明对应大小的区域:
- 先根据 nowtime 算出 m=2/1/0;
- 再确保 (x±m, y±m) 不越界,当前位置没有被访问过 (st[x][y]==false),覆盖区域内没有障碍物 '*';
- 都满足则返回 true。
4. 在 BFS 循环里:
- 如果时间 t 小于 2k,会向队列中再次压入当前坐标 (等待原地消耗时间);
- 若能移动到 (xx, yy) 且距离终点 (n-2, n-2) 时,就返回所需时间 t+1。
5. 最后输出最小到达终点的时间,不存在则返回 -1。
*/
const int N = 1e3 + 10;
int n, k;
char mp[N][N];
bool st[N][N];
// 用来存储位置与时间的结构体
struct state {
int x;
int y;
int time;
};
// 检查在时刻 nowtime,小明能否站在 (x,y)
bool check(int x,int y,int nowtime)
{
// 根据 nowtime 决定胖子体积(m 表示从中心到边界的偏移)
int m = 0;
if(nowtime < k) m = 2; // 5×5
else if(nowtime >= k && nowtime < 2*k) m = 1; // 3×3
else if(nowtime >= 2*k) m = 0; // 1×1
// 越界检查:如果 (x±m, y±m) 已经过界,则无法站立
if(x - m < 1 || x + m > n || y - m < 1 || y + m > n) return false;
// 如果该位置已被访问过,也不再重复
if(st[x][y]) return false;
// 遍历以 (x,y) 为中心,半径 m 的区域,看其中有没有障碍 '*'
for(int i = x - m; i <= x + m; i++) {
for(int j = y - m; j <= y + m; j++) {
if(mp[i][j] == '*') return false;
}
}
return true;
}
// 四个方向,用于 BFS 扩展
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
int bfs()
{
queue<state> q;
// 初始时刻小明在人为设定的起点 (3,3),时间为 0
q.push({3,3,0});
st[3][3] = true;
// BFS 主循环
while(!q.empty()) {
state t = q.front();
q.pop();
// 如果当前时间 < 2k,可以原地多等1单位时间,让小明消耗脂肪
if(t.time < 2*k) {
q.push({t.x,t.y,t.time+1});
}
// 尝试向四个方向移动
for(int i=0; i<4; i++) {
int xx = t.x + dx[i];
int yy = t.y + dy[i];
// 判断移动后当前位置是否可行
if(!check(xx, yy, t.time)) continue;
// 能走就把它加入队列,同时标记访问
q.push({xx,yy,t.time + 1});
st[xx][yy] = true;
// 如果到达终点 (n-2, n-2),直接返回用时
if(xx == n-2 && yy == n-2) return t.time + 1;
}
}
// BFS 结束还不能到终点,返回 -1
return -1;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> k;
// 读入迷宫(从 1 开始做偏移)
for(int i=1; i<=n; i++) {
cin >> mp[i]+1;
}
// 执行 BFS 并输出结果
int totaltime = bfs();
cout << totaltime;
return 0;
}
迷宫与陷阱
#include <bits/stdc++.h>
using namespace std;
/*
思路说明:
1. 首先定义一个结构体 Node,用来存储每个位置 (x, y) 所在坐标、当前剩余无敌步数 wudi,以及已经行走的步数 step。
2. 由于无敌状态会在到达带 '%' 的格子时重新补满 K 步,并且剩余无敌步数的不同会影响能否通过 'X' 格子,
所以我们需要一个三维访问数组 visited[x][y][w] 来记录:该坐标在剩余无敌步数为 w 时是否已经被访问过。
3. 从起点 (1, 1) 开始,根据其是否是无敌道具格子确定起始的无敌步数,并使用 BFS 扩展四周节点:
- 当下一步坐标越界或是墙 '#' 或者为陷阱 'X' 但当前无敌步数为 0 时,无法前进。
- 若下一步坐标是无敌道具 '%' 则将无敌步数重置为 K,否则将无敌步数减 1(但不低于 0)。
4. 每当走过一个格子后,将其标记为陷阱 'X'(体现题目思路中“走过就改成 X”),这样再次经过时就需要无敌才能通行。
5. 在 BFS 过程中,如果到达右下角则直接返回步数;若队列空了还没到达,则返回 -1。
*/
static const int MAXN = 1000 + 5;
struct Node {
int x; // 当前x坐标
int y; // 当前y坐标
int wudi; // 剩余无敌步数
int step; // 已走步数
};
int n, k;
char mp[MAXN][MAXN];
bool visited[MAXN][MAXN][15]; // visited[x][y][w] 表示在x,y坐标时,剩余无敌步数w是否访问过
int dx[4] = {1, -1, 0, 0};
int dy[4] = {0, 0, 1, -1};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> k;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= n; j++){
cin >> mp[i][j];
}
}
// BFS队列
queue<Node> q;
// 如果起点是无敌道具 '%', 则初始无敌步数设为 k,否则为 0
int startWudi = (mp[1][1] == '%') ? k : 0;
visited[1][1][startWudi] = true; // 标记该状态已访问
// 走过起点后,将其标记为 'X'
mp[1][1] = 'X';
// 将起点加入队列
q.push({1, 1, startWudi, 0});
while(!q.empty()) {
Node cur = q.front();
q.pop();
// 若已到达终点,输出所用步数并结束
if(cur.x == n && cur.y == n) {
cout << cur.step << "\n";
return 0;
}
// 向四个方向扩展
for(int i = 0; i < 4; i++) {
int nx = cur.x + dx[i];
int ny = cur.y + dy[i];
// 边界判断
if(nx < 1 || nx > n || ny < 1 || ny > n)
continue;
// 计算下一步剩余无敌步数
int nwudi = (cur.wudi > 0) ? cur.wudi - 1 : 0;
// 如果是墙 '#', 无法通过;如果是陷阱 'X' 且无敌步数已耗尽,也无法通过
if(mp[nx][ny] == '#') continue;
if(mp[nx][ny] == 'X' && cur.wudi == 0) continue;
// 如果下一步是无敌道具 '%', 则补充无敌步数为 k
if(mp[nx][ny] == '%') {
nwudi = k;
}
// 如果在 (nx, ny) 点,剩余无敌步数 nwudi 的状态尚未访问,则加入队列
if(!visited[nx][ny][nwudi]) {
visited[nx][ny][nwudi] = true;
// 将原位置存储一下,以便需要时可还原
// 题目想法为走过地块即变成 'X',下次如果没有无敌则不能再通过
char tmp = mp[nx][ny];
mp[nx][ny] = 'X';
q.push({nx, ny, nwudi, cur.step + 1});
// 如果想支持回退后可再次拿到道具,需要在这里根据 tmp 恢复地图
// 不过根据题意“之后再次到达同一格不会再次获得无敌”,
// 因此已走过的无敌道具格子也直接覆盖为 'X'
}
}
}
// 若 BFS 结束仍未到达终点,则返回 -1
cout << -1 << "\n";
return 0;
}
岛屿个数
#include <bits/stdc++.h>
using namespace std;
/*
思路概述:
1. 将输入的地图放大一圈,在最外层包围“海水”边界(mp[0..n+1][0..m+1]),
这样可以从 (0,0) 起始,用 8 个方向的 BFS 搜索海水,凡能搜索到的岛屿都不在“环”里。
2. 若在搜索海水的过程中碰到 ‘1’(陆地),再用 4 个方向的 BFS 将该陆地整个标记为已访问;
只要能被外部海水 BFS 搜索搜到,这个岛就不是被“环”封闭的,以此计数。所谓的子岛屿(完全被“环”包围的岛屿)则搜索不到。
3. 代码中 dx[]、dy[] 数组用作岛屿搜索(4 方向),dxx[]、dyy[] 数组用作海水搜索(8 方向)。
因为水可以从斜对角渗透到外部,所以需要 8 个方向;而陆地常规相邻只考虑上下左右相连即可。
*/
typedef pair<int,int> PII;
const int N=55;
int m, n; // 题目输入:m列, n行(或反之),注意与坐标索引的对应
char mp[N][N]; // 地图信息,'0' 表示海水, '1' 表示陆地
bool st[N][N]; // 标记数组,true 表示该坐标已被访问
int ans; // 记录可被搜到的岛屿数
// 岛屿搜索(4 个方向)
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
// 海水搜索(8 个方向)
int dxx[] = {-1, -1, 0, 1, 1, 1, 0, -1};
int dyy[] = {0, 1, 1, 1, 0, -1, -1, -1};
/*
功能:从某个陆地起点 (a,b) 开始,用 4 个方向 BFS 搜索同一岛屿,全部标记为已访问。
*/
void bfs_land(int a, int b)
{
queue<PII> q;
st[a][b] = true;
q.push({a, b});
while(!q.empty())
{
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
// 只用 4 个方向:上、右、下、左
for(int i = 0; i < 4; i++)
{
int xx = x + dx[i], yy = y + dy[i];
/*
注意边界判断:
这里我们允许索引在 [0..n+1] x [0..m+1] 的范围内,
因为外面包了一圈海水,所以最大可到 n+1、m+1。
*/
if(xx >= 0 && xx <= n + 1 && yy >= 0 && yy <= m + 1
&& mp[xx][yy] == '1' && !st[xx][yy])
{
st[xx][yy] = true;
q.push({xx, yy});
}
}
}
}
/*
功能:从外层海水 (0,0) 开始,用 8 个方向的 BFS 搜索所有能到达的海水。
若在搜索海水时遇到陆地,则用 4 方向 bfs_land() 搜索并标记这个岛,计数 +1。
最终的 ans 就是所有可与外部水连通的岛屿数(不包括被环封住的岛)。
*/
int bfs_hai()
{
queue<PII> q;
q.push({0, 0});
st[0][0] = true;
while(!q.empty())
{
auto t = q.front();
q.pop();
int x = t.first, y = t.second;
// 用 8 个方向搜索海水
for(int i = 0; i < 8; i++)
{
int xx = x + dxx[i], yy = y + dyy[i];
if(xx >= 0 && xx <= n + 1 && yy >= 0 && yy <= m + 1 && !st[xx][yy])
{
// 如果碰到陆地,则说明这个陆地与外部海水是连通的
if(mp[xx][yy] == '1')
{
bfs_land(xx, yy);
ans++;
}
else
{
// 若继续是海水,就继续将其加到队列做广搜
st[xx][yy] = true;
q.push({xx, yy});
}
}
}
}
return ans;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin >> T;
while(T--)
{
ans = 0;
cin >> m >> n;
// 每次都先将整张 mp 填充为 '0',表示最外围也都是海水
memset(mp, '0', sizeof(mp));
memset(st, false, sizeof(st));
// 读入地图主体:若 i <= n, j <= m,这里相当于行 i, 列 j
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
cin >> mp[i][j];
}
}
// 从外部海水(0,0)开始向内搜索
cout << bfs_hai() << "\n";
}
return 0;
}
填涂颜色
#include <bits/stdc++.h>
using namespace std;
/*
思路说明(注释在代码中):
1. 整个方阵外围再包一圈,这样可在 (0,0) 处开始 BFS,访问到所有可直达外边界的 0。
2. 对能从外边直接走到的 0 标记 visited;那些无法从外边走到的 0,即闭合圈内部的 0。
3. BFS0():以 (0,0) 起点,沿着 mp[][] == 0 的位置遍历,如果遇到 1 则 BFS1() 跳过那圈的其他 1。
4. 最终输出时,若某 (i,j) 未被访问,那就是闭合圈内的 0 填成 2,否则维持原值。
*/
typedef pair<int,int> PII; // 储存坐标的常用类型对
static const int N=40; // n 最大 30,这里开到 40 防越界
int n;
bool st[N][N]; // 标记是否已访问
int mp[N][N]; // 存储输入矩阵
// 四个方向
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
// 当扫描到一个 1 时,用 bfs1() 来遍历它相连的 1(不让 BFS0() 继续绕过去)
void bfs1(int a,int b)
{
queue<PII> q;
q.push({a,b});
st[a][b]=true;
while(!q.empty())
{
auto t=q.front();
q.pop();
int x=t.first, y=t.second;
// 继续向四邻域扩散,但必须仍是 1
for(int i=0;i<4;i++){
int xx=x+dx[i], yy=y+dy[i];
// 在边界内,且未访问,且值 ==1
if(xx>=0 && xx<=n+1 && yy>=0 && yy<=n+1 && !st[xx][yy]){
if(mp[xx][yy]==1){
st[xx][yy]=true;
q.push({xx,yy});
}
}
}
}
}
// bfs0():从外边 (0,0) 出发,遍历所有可达的 0;若遇到 1,则使用 bfs1() 跳过那片 1。
void bfs0()
{
queue<PII> q;
q.push({0,0});
st[0][0]=true;
while(!q.empty()){
auto t=q.front();
q.pop();
int x=t.first, y=t.second;
// 四个方向继续走
for(int i=0;i<4;i++){
int xx=x+dx[i], yy=y+dy[i];
if(xx>=0 && xx<=n+1 && yy>=0 && yy<=n+1 && !st[xx][yy]){
// 如果是 1,则调用 bfs1() 跳过那圈 1
if(mp[xx][yy]==1){
bfs1(xx,yy);
}
// 如果是 0,则在这里继续互联
else{
st[xx][yy] = true;
q.push({xx,yy});
}
}
}
}
}
int main()
{
cin >> n;
// 读入时将有效内容放到 [1..n][1..n]
// 外面多留一圈是为了能从 (0,0) 起点 BFS
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin >> mp[i][j];
}
}
// 从 (0,0) 出发,可达的 0 位置全部标记
bfs0();
// 最终判断哪些 0 未被标记,那些就在闭合圈内 -> 输出 2
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(!st[i][j] && mp[i][j] == 0){
cout << 2 << " ";
}
else{
cout << mp[i][j] << " ";
}
}
cout << "\n";
}
return 0;
}
最后的迷宫
二维数组写法(内存超限)
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
static const int N = 1e3;
static const int INF = 0x3f3f3f3f; // 用于表示不可达
int n, m;
char mp[N][N];
int dista[N][N];
bool st[N][N];
int startx, starty, endx, endy;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
// 用于从奖杯位置检查八个方向的“直线可视”
int ddx[8] = {0, 0, 1, 1, 1, -1, -1, -1};
int ddy[8] = {1, -1, -1, 0, 1, -1, 0, 1};
// 在 BFS 前,将 dista[][] 初始化为 -1,表示不可达
// BFS 后如果 dista[x][y] 仍 == -1,则说明从起点到该点不可达
void bfs() {
queue<PII> q;
// 起点距离置为 0,标记访问
dista[startx][starty] = 0;
st[startx][starty] = true;
q.push({startx, starty});
while(!q.empty()) {
auto [x, y] = q.front();
q.pop();
// 四方向扩散
for(int i = 0; i < 4; i++){
int xx = x + dx[i];
int yy = y + dy[i];
// 保证在边界内,未访问过,且不是墙
if(xx >= 1 && xx <= n && yy >= 1 && yy <= m
&& !st[xx][yy] && mp[xx][yy] == 'O')
{
// 更新距离并压入队列
dista[xx][yy] = dista[x][y] + 1;
st[xx][yy] = true;
q.push({xx, yy});
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
// 读入迷宫
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> mp[i][j];
}
}
// 不断读入 奖杯位置 (endx, endy) 和 起点 (startx, starty)
while(true){
cin >> endx >> endy >> startx >> starty;
if(!cin || (startx == 0 && starty == 0 && endx == 0 && endy == 0)){
break; // 结束条件
}
// 重置访问数组和距离数组
memset(st, false, sizeof(st));
memset(dista, -1, sizeof(dista));
// BFS 计算最短距离
bfs();
// 用 ans 存最小步数,初始化为 INF
int ans = INF;
// 如果奖杯格子本身可到达,则尝试更新 ans
if(dista[endx][endy] != -1){
ans = min(ans, dista[endx][endy]);
}
// 再对奖杯相邻的 8 个方向做“直线可视”检查
for(int i = 0; i < 8; i++){
int xx = endx, yy = endy;
// 沿这条方向继续“前进”直到越界或遇到墙
while(true){
int nx = xx + ddx[i];
int ny = yy + ddy[i];
if(nx < 1 || nx > n || ny < 1 || ny > m) break; // 越界
if(mp[nx][ny] != 'O') break; // 墙了
xx = nx;
yy = ny;
// 如果此处 BFS 可达,则更新 ans
if(dista[xx][yy] != -1){
ans = min(ans, dista[xx][yy]);
}
}
}
// 若最终 ans 仍为 INF,表示无法到达视野内
if(ans == INF){
cout << "Poor Harry" << "\n";
} else {
cout << ans << "\n";
}
}
return 0;
}
一维写法
#include <bits/stdc++.h>
using namespace std;
/*
* 修改思路:
* 1. 将二维 char mp[N][N] 和 int dist[N][N] 的方式改为像第一个代码那样,
* 使用一维数组 string s[] 存储地图,使用 vector<int> dis[] 作为距离数组。
* 2. 读入 n 行字符串,每行长度为 m,然后将 dis[i] 初始化为大小 m(或 m+1),初值为 INF。
* 3. BFS 时使用 dis[x][y] != INF 判断是否已访问,类似第一个代码。
* 4. 坐标下标改为 0~n-1, 0~m-1,便于与 string 的下标一致。
*/
static const int INF = 0x3f3f3f3f;
// 四个方向
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
// 八个方向
int ddx[8] = {0, 0, 1, 1, 1, -1, -1, -1};
int ddy[8] = {1, -1, -1, 0, 1, -1, 0, 1};
struct Node {
int x, y;
};
int n, m;
// 地图存储:s[i] 表示第 i 行字符串,长度为 m
string s[16385];
// dis[i][j] 改为 dis[i][j] (通过 vector<int> dis[i])
vector<int> dis[16385];
/* 判断 (x,y) 是否在边界内,且为 'O' */
bool ok(int x, int y) {
return (x >= 0 && x < n && y >= 0 && y < m && s[x][y] == 'O');
}
/* BFS,计算从 (sx, sy) 到各位置的最短距离 */
void bfs(int sx, int sy) {
// 初始化 dis[][] = INF
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
dis[i][j] = INF;
}
}
queue<Node> q;
q.push({sx, sy});
dis[sx][sy] = 0;
while (!q.empty()) {
auto [x, y] = q.front();
q.pop();
for (int i = 0; i < 4; i++) {
int nx = x + dx[i];
int ny = y + dy[i];
if (ok(nx, ny) && dis[nx][ny] == INF) {
dis[nx][ny] = dis[x][y] + 1;
q.push({nx, ny});
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
// 读入迷宫行
for (int i = 0; i < n; i++) {
cin >> s[i];
// 每行的距离数组开到 m 大小,初始化在 BFS 中做
dis[i].resize(m);
}
int sx, sy, ex, ey;
// 多次读入起终点
while (true) {
cin >> ex >> ey >> sx >> sy;
// 如果四个值都为 0,说明要结束
if (!cin || (ex == 0 && ey == 0 && sx == 0 && sy == 0)) {
break;
}
// 转换为 0-based
ex--, ey--, sx--, sy--;
// 先做 BFS,自 (sx, sy) 求到所有位置最短距离
bfs(sx, sy);
// 初始 ans 为到 (ex, ey) 的距离
int ans = (ex >= 0 && ex < n && ey >= 0 && ey < m) ? dis[ex][ey] : INF;
// 然后从奖杯 (ex, ey) 出发,用 8 方向直线扫描
for (int i = 0; i < 8; i++) {
int x = ex, y = ey;
while (ok(x + ddx[i], y + ddy[i])) {
x += ddx[i];
y += ddy[i];
ans = min(ans, dis[x][y]);
}
}
// 如果还是 INF,说明无法到达能“看见”奖杯的位置
if (ans == INF) {
cout << "Poor Harry\n";
} else {
cout << ans << "\n";
}
}
return 0;
}
快速幂
模板
基于位运算
using ll = long long; // 可以在头文件中添加这行
ll qmi(ll a, ll b, ll c)
{
ll ans = 1; // 初始化结果为 1
a %= c; // 将 a 取模 c,确保 a 小于 c
while (b) // 当 b 不为零时循环
{
if (b & 1) // 如果 b 是奇数
{
ans = (ans * a) % c; // 更新结果
}
a = (a * a) % c; // 将 a 平方并取模 c
b >>= 1; // 将 b 右移一位(相当于除以 2)
}
return ans; // 返回结果
}
小数第n位
#include <iostream>
using namespace std;
using ll=long long;
ll a,b,n;
ll qmi(ll a,ll b,ll c)
{
ll ans=1;
a%=c;
while(b)
{
if(b&1)
{
ans=(ans*a)%c;
}
a=(a*a)%c;
b>>=1;
}
return ans;
}
int main()
{
cin>>a>>b>>n;
ll x=a*qmi(10,n-1,b)%b;
cout <<10*x / b;
x=10*x%b;
cout<<10*x/b;
x=10*x%b;
cout<<10*x/b;
return 0;
}
方阵幂次
越狱
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 模数
static const int MOD = 100003;
/**
* qmi 函数:快速幂取模
* a:底数
* b:指数
* c:模数
* 实现思路:通过二进制拆分指数,每次让 a 在循环中平方,b 在循环中右移一位。
* 如果 b 的当前二进制位为 1,则先将结果乘以当前 a。
*/
ll qmi(ll a, ll b, ll c) {
ll ans = 1;
a %= c;
while(b) {
if(b & 1) ans = (ans * a) % c;
a = (a * a) % c;
b >>= 1;
}
return ans;
}
int main() {
ll m, n;
cin >> m >> n;
/**
* 思路:
* 1. 监狱共有 n 间相邻房间,m 种宗教。
* 2. 题目要求的“可能越狱”状态数可由以下分析得出:
* - m^n 表示所有状态数(每个房间都可以从 m 种宗教中任选一种)。
* - 要使犯人相邻且宗教相同,则至少有一组相邻房间完全相同。
* 结合容斥思想,要求相邻相同至少发生一次。
* 但根据题目推导可简化为 m^n - m*(m-1)^(n-1),
* 其中 (m-1)^(n-1) 表示第一个人固定一种宗教,其余房间都和上一个房间“不同宗教”。
* 再乘上 m,代表第一个人可能是任意一种宗教。
* 3. 结果最后要对 MOD (100003) 取模。
*/
ll res = qmi(m, n, MOD) - (m % MOD) * qmi(m - 1, n - 1, MOD) % MOD;
// 输出时加上 MOD 再取模,避免出现负值。
cout << (res + MOD) % MOD << endl;
return 0;
}
智子封锁
#include<bits/stdc++.h>
using namespace std;
int main(){
int a, b, x, res = 1, mod = 10;
cin >> a >> b >> x;
if(x == 1) mod = 10;
else if(x == 2) mod = 100;
else if(x == 3) mod = 1000;
else mod = 10000;
for(int i = 1 ; i <= b ; i ++){
res = res * a % mod;
}
cout << res << '\n';
return 0;
}
合法奇数
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const ll MOD= 1000000007;
int main()
{
ll ans=1,a=3,n;
cin>>n;
n-=2;
while(n)
{
if(n&1)ans=(a*ans)%MOD;
a=(a*a)%MOD;
n>>=1;
}
ans=(ans*2)%MOD;
cout<<ans;
return 0;
}
不过一瞬息
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
/**
* qmi(a, b, c): 快速幂取模函数
* 计算 a^b % c
*/
ll qmi(ll a, ll b, ll c)
{
ll ans = 1;
a %= c;
while (b)
{
if (b & 1)
{
ans = (ans * a) % c;
}
a = (a * a) % c;
b >>= 1;
}
return ans;
}
int main()
{
ll res = 0;
// 计算从 1 到 2022 的和
// f(n) = n^(n^n) 对 7 取余
// 具体做法:先计算 (n^n % 7),再用结果做底数计算 ^n % 7
for (int i = 1; i <= 2022; i++)
{
res += qmi(qmi(i, i, 7), i, 7);
}
// 输出最终和的值,不要求对结果再做任何取模
cout << res << endl;
return 0;
}
双指针
小齐的奶牛配对挤奶计划
样例输入
3
1 8
2 5
1 2
样例输出
10
评测数据规模
1 ≤ M ≤ 1 , 000 , 000 , 000 , M 为偶数, 1 ≤ N ≤ 100 , 000 1≤M≤1,000,000,000,M 为偶数,1≤N≤100,000 1≤M≤1,000,000,000,M为偶数,1≤N≤100,000
#include <bits/stdc++.h>
using namespace std;
/*
问题描述:
给定若干组输入 (x, y),表示有 x 头奶牛,其挤奶产量为 y。
这些 input 的 x 之和为 M(总奶牛数,且 M 为偶数)。
将所有 M 头奶牛分成 M/2 对,并行挤奶时的总耗时,取决于所有配对 (A, B) 的挤奶时间 A+B 的最大值。
目标是找到所有可能配对中,使 max(A+B) 最小的方案,并输出这个值。
解决思路(双指针):
1. 将每种产量 y 与其数量 x 记录下来,并按照 y 升序排序。
2. 设置两端指针:left 指向最小产量,right 指向最大产量。
3. 每次取尽可能多的奶牛对,数量为 min(左侧剩余奶牛数, 右侧剩余奶牛数)。
4. 该批次配对的时间为 left 产量 + right 产量,用其更新全局最大值。
5. 逐步减少两侧数量并移动指针,直至全部奶牛被配对完成。
时间复杂度主要在对产量排序上,为 O(N log N),其中 N 最多为 100000(不按单头奶牛数量计)。
*/
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
cin >> N;
// 记录 (y, x)
vector<pair<long long, long long>> cows;
cows.reserve(N);
long long totalCount = 0; // 用于计算奶牛总数 M
for (int i = 0; i < N; ++i) {
long long x, y;
cin >> x >> y;
cows.push_back({y, x});
totalCount += x;
}
// 按产量升序排序
sort(cows.begin(), cows.end(), [](auto &a, auto &b){
return a.first < b.first;
});
// 双指针:l 指向产量最小,r 指向产量最大
int l = 0, r = (int)cows.size() - 1;
long long res = 0;
while (l <= r) {
if (l == r) {
// 剩余都在同一个产量上
// 这时必然剩余的奶牛数为偶数,可以两两配对
// 配对时间为 2 * cows[l].first
// 但实际只需要一次就能给出最终答案
res = max(res, 2LL * cows[l].first);
break;
}
// 本轮可以配对的奶牛数
long long num = min(cows[l].second, cows[r].second);
// 对应配对时间
long long sumTime = cows[l].first + cows[r].first;
res = max(res, sumTime);
// 扣除配对过的奶牛数
cows[l].second -= num;
cows[r].second -= num;
// 如果左侧产量用完,则左指针右移
if (cows[l].second == 0) {
++l;
}
// 如果右侧产量用完,则右指针左移
if (cows[r].second == 0) {
--r;
}
}
cout << res << "\n";
return 0;
}
卓儿探寻全球变暖
样例输入
5 3
1 3 5 1 3
0 2 4
样例输出
1 2 1
1 ≤ n , d ≤ 1 0 5 , 1 ≤ h i ≤ 1 0 9 1≤n,d≤10^5,1≤h_i≤10^9 1≤n,d≤105,1≤hi≤109
暴力做法
变量含义:
• n 表示大楼数量,d 表示要查询的天数。
• 数组 h 存储每栋大楼的高度,数组 t 存储每个查询日对应的海平面高度。
• 布尔数组 st 标记某天是否“未被淹没”(true 为未淹没)。
对每个查询日的处理:
(1) 将大楼中高 <= 当前海平面的全部标记为 false(表示已淹没)。
(2) 随后扫描所有大楼,累积计算未淹没大楼所形成的连续“区域”数量:
- 如果遇到一段连续的 true(未淹没大楼),则算作一个区域;
- 当连续的 true 被一个 false(淹没大楼)打断时,再出现下一段 true 时,就会有一个新的区域。
(3) 将最终区域数输出。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 10;
// h[] 用于存储每栋大楼的高度
// t[] 用于存储不同查询日的海平面高度
// st[] 用于标记某栋大楼今日是否仍未被淹没
vector<int> h(N);
vector<int> t(N);
bool st[N];
int main() {
int n, d;
// 输入 n(大楼数量)和 d(查询天数)
cin >> n >> d;
// 读取大楼高度
for (int i = 1; i <= n; i++) {
cin >> h[i];
}
// 读取查询海平面天数数组
for (int i = 1; i <= d; i++) {
cin >> t[i];
}
// 将 st[] 初始化为 true,表示初始默认所有大楼都未被淹没
memset(st, true, sizeof(st));
// 对每个查询天数分别进行处理
int region = 0; // 表示当前查询日下,未淹没大楼所形成的区域数量
for (int i = 1; i <= d; i++) {
int day = t[i]; // 当前海平面高度
region = 0; // 每次查询前重置区域数
bool flag = false; // 标记是否在扫描大楼时已经遇到一个“连续未淹没区域”
// 1) 更新 st[j]: 若大楼高度 <= 当前海平面,则标记为已被淹没 (false)
for (int j = 1; j <= n; j++) {
if (h[j] <= day) {
st[j] = false;
}
}
// 2) 统计当前未淹没楼形成的连续区域数
for (int j = 1; j <= n; j++) {
if (st[j]) {
// 如果此楼未淹没并且尚未记录一个新区域,则区域数加一
if (!flag) {
region++;
flag = true; // 进入新区域
}
} else {
// 如果此楼已被淹没,则结束之前的未淹没区域标记
flag = false;
}
}
// 输出当日的区域数
cout << region << " ";
}
return 0;
}
双指针+排序
- 首先,将所有大楼 (高度, 下标) 按高度从小到大排序,便于后续根据海平面高度逐步淹没。
- 随后,有一个双指针循环:当海平面上升到 t[i] 时,就把所有高度 ≤ t[i] 的大楼“标记”为淹没(分别存储到 drown[i] 列表)。
- 利用一个布尔数组 st[] 来标记下标为 x 的大楼是否已被淹没;给 0 与 n+1 这两个“边界”强制设为已淹没状态(true),方便识别某大楼左右是否都是淹没状态。
- 遍历 drown[i],对于每一个新淹没的大楼 x:
• 若 x 左右均尚未淹没,则此次淹没会把原来的一个区域分割成两个,因此区域计数 ans 增加 1。
• 若 x 左右均已经淹没,则原先的两个淹没区在 x 处“连接”为一个区,区域计数 ans 减少 1。
• 最后将 x 标记为已淹没。 - 每次处理完后,输出当前 ans 值,即此刻剩余未淹没大楼的整体区域数量。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e5 + 10;
bool st[N];
int main() {
int n, d;
cin >> n >> d;
vector<pair<int,int>>h(n+1);// h[]数组存储(大楼高度, 其原始下标),下标从1开始使用
for (int i = 1; i <= n; i++)
{
cin>>h[i].first;// 读入大楼高度
h[i].second=i; // 存储对应的原始位置
}
// t[]数组存储每个查询的海平面高度(总共有d次查询)
vector<int> t(d);
for (int i = 0; i < d; i++)
{
cin >> t[i]; // 读入第i次查询的海平面高度
}
// 将 h 中的大楼数据按高度升序进行排序
sort(h.begin() + 1, h.end());
// drown[i]存储在第i次查询中「新被淹没」的大楼下标
vector<vector<int>>drown(d);
// 双指针:i遍历查询,j遍历大楼列表
// 若 h[j+1].first <= t[i] => 说明该楼在第i次查询时已经被淹没
// 则记录其位置到drown[i]中表示本轮新增被淹没的楼
for (int i = 0, j = 0; i < d; i++) {
while (j + 1 <= n && h[j + 1].first <= t[i]) {
j++;
drown[i].emplace_back(h[j].second);
}
}
// st[x]用于标记下标为x的楼是否已被淹没
// 在边界0和n+1处设置为true,方便判断左右是否淹没
st[0] = true; // 边界视为已淹没
st[n + 1] = true; // 边界视为已淹没
int ans = 1; // 当前未淹没的大楼形成的区域数,初始设为1
// 遍历每个查询,将在该日新增被淹没的楼进行处理
for(auto &u:drown)
{
// u中存储了本次查询“刚好在海平面下”的大楼索引
for(auto x:u)
{
// 情况1:若左右都未被淹,则淹没x会把一个连续区域分成两部分 => 区域数 +1
if(!st[x-1]&&!st[x+1])
{
ans+=1;
}
// 情况2:若左右都已淹,则淹没x会将两个淹没区“连接”,相当于减少一个区域 => 区域数 -1
if (st[x - 1] && st[x + 1]) {
ans -= 1;
}
st[x]=true;
}
cout<<ans<<" ";
}
return 0;
}