竞赛题目链接
#include
#include
#include
#include
using namespace std;
const int N = 100010;
unordered_map<char, int> mp;
int main()
{
int a1, a2, a3, a4;
cin >> a1 >> a2 >> a3 >> a4;
mp['1'] = a1, mp['2'] = a2, mp['3'] = a3, mp['4'] = a4;
string s;;
cin >> s;
int res = 0;
for (int i = 0; i < s.size(); i ++ )
res += mp[s[i]];
printf("%d\n", res);
return 0;
}
算法思路: 双指针
#include
#include
#include
using namespace std;
const int N = 500010, M = 1000010;
int n, m;
int w[N], cnt[M]; // w存储数值,cnt[i]表示i在区间中的个数
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
int res = 0, l, r; // res表示最长区间长度,l、r分别表示区间左右端点
for (int i = 1, j = 1, t = 0; i <= n; i ++ ) // i、j表示指针,t表示区间不同元素个数
{
// 如果该值是首次出现
if (cnt[w[i]] == 0) t ++ ;
cnt[w[i]] ++ ;
// 从区间尾部弹出去一个
while (t > m)
{
// 如果该区间内只有它一个
if (cnt[w[j]] == 1) t -- ;
cnt[w[j]] -- ;
j ++ ;
}
if (i - j + 1 > res)
{
res = i - j + 1;
l = j, r = i;
}
}
printf("%d %d\n", l, r);
return 0;
}
算法思路:
求子矩阵和的方法:
对于一个任意连续子矩阵,长边是(a1, a2, a3)
,宽边是(b1, b2 b3)
,则子矩阵的和为a1 * (b1 + b2 + b3) + a2 * (b1 + b2 + b3) + a3 * (b1 + b2 + b3) = (a1 + a2 + a3) * (b1 + b2 + b3)
,所以子矩阵的和就等于子矩阵的(长边之和) * (宽边之和)
;
后面的解释看代码吧,视频讲解我反而感觉不好理解。这个题目的关键就是要能将求任意子矩阵面积的最大值
等价成求长和宽区间长度乘积最大且,该长度的区间上的和的最小值乘积要小于x
。
Tips: 枚举每个长度下该长度内每个值之和的最小值的代码。
#include
#include
#include
using namespace std;
const int N = 2010, INF = 1e9;
int n, m, x;
int s1[N], s2[N]; // 分别表示第一个和第二个前缀和
int a[N], b[N]; // 分别表示每个数组长度中的最小值
int main()
{
scanf("%d%d", &n, &m);
// 计算第一个数组前缀和
for (int i = 1; i <= n; i ++ )
{
int x;
scanf("%d", &x);
s1[i] = s1[i - 1] + x;
}
// 计算第二个数组前缀和
for (int i = 1; i <= m; i ++ )
{
int x;
scanf("%d", &x);
s2[i] = s2[i - 1] + x;
}
// 枚举每个长度下该长度内每个值之和的最小值
for (int len = 1; len <= n; len ++ )
{
a[len] = INF;
for (int i = 1; i + len - 1 <= n; i ++ )
{
int j = i + len - 1;
a[len] = min(a[len], s1[j] - s1[i - 1]);
}
}
for (int len = 1; len <= m; len ++ )
{
b[len] = INF;
for (int i = 1; i + len - 1 <= m; i ++ )
{
int j = i + len - 1;
b[len] = min(b[len], s2[j] - s2[i - 1]);
}
}
int res = 0;
scanf("%d", &x);
for (int i = 1, j = m; i <= n; i ++ ) // 枚举每个区间长度
{
while (j && b[j] > x / a[i]) // 这里除法处理更好,防止溢出
j -- ;
res = max(res, i * j);
}
printf("%d\n", res);
return 0;
}
LeetCode 2224
算法思路:
先统计出两个时间差的分钟数,然后在使用贪心的思路求出最少操作数。
class Solution {
typedef pair<int, int> PII;
public:
int convertTime(string current, string correct) {
// 处理和存储数据
PII cu, co;
cu.first = (current[0] - '0') * 10 + current[1] - '0';
cu.second = (current[3] - '0') * 10 + current[4] - '0';
co.first = (correct[0] - '0') * 10 + correct[1] - '0';
co.second = (correct[3] - '0') * 10 + correct[4] - '0';
int res = 0, cnt = 0; // res表示最少操作数,cnt表示时间差额
cnt = (co.first - cu.first) * 60 + (co.second - cu.second);
int up[4] = {1, 5, 15, 60};
for (int i = 3; i >= 0; i -- )
{
int j = up[i];
while (cnt / j)
{
res += cnt / j;
cnt = cnt % j;
}
}
return res;
}
};
LeetCode 2225
算法思路:
通过map来记录每个人的输的场数(不需要开一个map来记录赢得场数),最后通过输的场数来返回值。
class Solution {
public:
vector<vector<int>> findWinners(vector<vector<int>>& matches) {
vector<int> w, l;
map<int, int> mp;
for (auto c : matches)
{
mp[c[0]]; // 胜利的人
mp[c[1]] ++ ; // 输了的人
}
for (auto c : mp)
{
if (c.second == 0) // 全胜的人
w.push_back(c.first);
if (c.second == 1) // 恰赢一场的人
l.push_back(c.first);
}
return vector<vector<int>>{w, l};
}
};
LeetCode 2226
算法思路: 可以将其转换为二分问题来做。具体思路看官方题解。
同样的思路可以解决这个题目:2187. 完成旅途的最少时间
class Solution {
public:
int maximumCandies(vector<int>& candies, long long k) {
long long sum;
for (auto c : candies)
sum += c;
// 分别表示可以分得的最少和最多的糖果数目
long long low = 0, high = sum / k;
while (low != high)
{
long long mid = (low + high + 1) / 2; // 每个人获得mid个糖果
long long heap = 0; // 按每个人mid个糖果分配可以分得的堆数
for (int num : candies)
heap += num / mid;
/* 如果当前分得的堆的数目小于小孩的的数目,使mid减小,即high减小 */
if (heap < k)
high = mid - 1;
/* 这里利用的是贪心:如果k=mid,则希望找到更多地mid,就增加mid,即low增大 */
else
low = mid;
}
return (int)low;
}
};
题目链接
需要对start
和goal
不同的二进制位进行操作,所以先对它们进行异或运算,再统计出异或运算结果中1
的个数即可。
class Solution {
public:
int minBitFlips(int start, int goal)
{
int res = 0, x = start ^ goal;
while (x)
{
res += x & 1;
x = x >> 1;
}
return res;
}
};
题目链接
class Solution {
public:
int triangularSum(vector<int>& nums)
{
int n = nums.size();
for (int i = n - 2; i >= 0; i -- )
for (int j = 0; j <= i; j ++ )
nums[j] = (nums[j] + nums[j + 1]) % 10;
return nums[0];
}
};
题目链接
这个题目的含义实质上是求子序列为010
或者101
的个数。
算法思路1:动态规划
举例解释:
对于序列01001
,有2
个10
,4
个01
0
,序列变成010010
,则01
的数量依然是4
;1
,序列变成010011
,则10
的数量依然是2
;因此有:
0
会增加010
的个数,同时会增加10
的个数;1
会增加101
的个数,同时会增加01
的个数;class Solution {
public:
long long numberOfWays(string s) {
long long ans = 0, n0 = 0, n1 = 0, n10 = 0, n01 = 0;
for (char i : s)
{
/*
循环不变式(loop invariant)
n0 等于 s.substr(0,i)中 值为 "0" 的子序列的数量
n1 等于 s.substr(0,i)中 值为 "1" 的子序列的数量
n10 等于 s.substr(0,i)中 值为 "10" 的子序列的数量
n01 等于 s.substr(0,i)中 值为 "01" 的子序列的数量
对于新增的字符s[i], 讨论以s[i]结尾的所有新增子序列, 然后更新n0,n1,n10,n01
*/
if (i == '1')
{
n01 += n0;
n1 ++ ;
ans += n10;
}
else
{
n10 += n1;
n0 ++ ;
ans += n01;
}
}
return ans;
}
};
算法思路2:前缀和
对任意一个位置,以它为中心构建合法相邻建筑的数量,分两种情况讨论:
0
,该位置左侧1
的数量*
该位置右侧1
的数量。这样可以构成101
;1
,该位置左侧0
的数量*
该位置右侧0
的数量。这样可以构成010
。所以我们可以:
0
或者1
的数量。 统计0
还是1
取决于该位置是1
还是0
;0
或者1
的数量 ;0
或1
数量,和右侧0
或1
数量相乘,即为以该位置贡献的答案数量,对每个位置的贡献量求和,即为返回答案。class Solution {
public:
long long numberOfWays(string s) {
int n = s.size();
long long ans = 0;
int count[n]; // 用于统计左右侧字符的数量
memset(count, 0, sizeof count);
int zeroCount = 0, oneCount = 0;
// 从右往左遍历,统计每个位置右侧0或1字符的数量
for (int i = n - 1; i >= 0; i -- )
{
if (s[i] == '0') count[i] = oneCount; // 如果位置为0,统计右侧1的数量
else count[i] = zeroCount; // 如果位置为1,统计右侧0的数量
zeroCount += s[i] == '0' ? 1 : 0;
oneCount += s[i] == '1' ? 1 : 0;
}
zeroCount = 0, oneCount = 0;
// 从左往右遍历,统计每个位置左侧0或者1字符的数量。这里我们与右侧数量直接相乘得到该位置贡献量
for (int i = 0; i < n; i ++ )
{
if (s[i] == '0') count[i] *= oneCount; //该位置为0,则统计左侧1的数量,并与右侧数量相乘
else count[i] *= zeroCount; //该位置为1,则统计左侧0的数量,并与右侧数量相乘
zeroCount += s[i] == '0' ? 1 : 0;
oneCount += s[i] == '1' ? 1 : 0;
ans += count[i];
}
return ans;
}
};
题目链接:LT 6037
这给题要注意审题,题目要求是将所给正整数中每个数奇偶性相同
,而不是每个数下标
的奇偶性相同!
注意两个函数:
数值
转 string
:to_string(int / long / double...)
string
转 int / float / long
:atoi(string.c_str()) / atof(string.c_str()) / atol(string.c_str())
class Solution {
public:
int largestInteger(int num) {
string str = to_string(num);
for (int i = 0; i < str.size(); i ++ )
for (int j = str.size() - 1; j >= i; j -- )
if ((str[i] - str[j]) % 2 == 0 && str[i] < str[j])
swap(str[i], str[j]);
return atoi(str.c_str());
}
};
题目链接:LT 2232
注意对几个函数的使用:find()、substr()、stoi()、to_string()
class Solution {
public:
string minimizeResult(string expression) {
int n = expression.size();
int mid = expression.find('+');
int best = 2e9;
string ans;
for (int i = 0; i < mid; i ++ )
{
for (int j = mid + 1; j < n; j ++ )
{
int p = (i == 0 ? 1 : stoi(expression.substr(0, i)));
int q = stoi(expression.substr(i, mid - i));
int r = stoi(expression.substr(mid + 1, j - mid));
int s = (j == n - 1 ? 1 : stoi(expression.substr(j + 1, n - j - 1)));
int result = p * (q + r) * s;
if (result <= best)
{
best = result;
ans = expression.substr(0, i) + "(" + expression.substr(i, j - i + 1) + ")" + expression.substr(j + 1, n - j - 1);
}
}
}
return ans;
}
};
使用优先队列,可以证明每次将最小的数 +1
的k
次操作后可以得到最大乘积。
注意: 最结果的取模运算,容易出错。
class Solution {
public:
int maximumProduct(vector<int>& nums, int k) {
priority_queue<int, vector<int>, greater<int>> q;
int MOD = 1000000007;
long long res = 1; // 注意这里的long long
for (auto c : nums)
q.push(c);
for (int i = 0; i < k; i ++ )
{
int x = q.top();
q.pop();
x ++ ;
q.push(x);
}
while (!q.empty())
res = (res * q.top()) % MOD, q.pop();
return (int)res;
}
};
题目链接ACWing 4396
#include
using namespace std;
int n1, n2, k1, k2;
int main()
{
cin >> n1 >> n2 >> k1 >> k2;
if (n1 <= n2)
puts("Second");
else
puts("First");
return 0;
}
题目链接:ACWing 4397
算法思路:
对于每一张牌求出其正面和反面数值的差值d[i]
以及正面所有数值的和sum
,要使数值尽可能地小且正面朝上的卡牌数量不少于k
,则只需要减去d[i]
中< 0
最小的n - k
张牌即可。
#include
#include
using namespace std;
const int N = 2e5 + 10;
int n, k, sum;
int a[N], b[N], d[N];
int main()
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]), sum += a[i];
for (int i = 1; i <= n; i ++ ) scanf("%d", &b[i]), d[i] = b[i] - a[i];
sort(d + 1, d + n + 1);
for (int i = 1; i <= n - k; i ++ )
{
if (d[i] >= 0) break;
else sum += d[i];
}
printf("%d\n", sum);
return 0;
}
题目链接:ACWing 4398
代码一: 暴力枚举,会TLE
每一次将每个字符串在已给出的字符串中进行查找,找到res++
,这样会超时。
#include
#include
#include
#include
using namespace std;
const int N = 50010;
int n, q;
string str[N];
string p;
int main()
{
scanf("%d", &n);
for(int i = 0; i < n; i ++ )
cin >> str[i];
scanf("%d", &q);
while (q -- )
{
int res = 0, k = 0;
cin >> p;
for(int j = 0; j < n; j ++ )
{
if (str[j].find(p) != str[j].npos)
{
res ++ ;
k = j;
}
}
if (res)
printf("%d %s\n", res, str[k].c_str());
else
printf("0 -\n");
}
return 0;
}
代码二:
由题可知,每个字符串长度是1~8
,所以一个字符串(长度为8
)最多有8 + 7 + 6 ... + 1 = 36
个子串。一共有1e5
个字符串,总共3.6e5
个字符串。
unordered_map
:对于每个询问子串p
,有多少个字符串包含这个子串;
unoedered_map
:对于每个询问子串p
,包含这个子串的字符串中的某一个;
#include
#include
#include
#include
#include
using namespace std;
int main()
{
unordered_map<string, int> cnt;
unordered_map<string, string> hash;
int n, m;
cin >> n >> m;
while (n -- )
{
string str;
cin >> str;
unordered_set<string> S; // 存储字符串的所有子串,方便判重
// 枚举所有子串
for (int i = 0; i < str.size(); i ++ )
for (int j = i; j < str.size(); j ++ )
S.insert(str.substr(i, j - i + 1));
for (auto& s : S)
{
cnt[s] ++ ;
hash[s] = str;
}
}
cin >> m;
while (m -- )
{
string str;
cin >> str;
cout << cnt[str] << ' ';
if (!cnt[str]) puts("-");
else cout << hash[str] << endl;
}
return 0;
}
题目链接
#include
#include
#include
#include
using namespace std;
int main()
{
unordered_set<char> hash;
char c;
while (cin >> c)
if (c >= 'a' && c <= 'z')
hash.insert(c);
cout << hash.size() << endl;
return 0;
}
题目链接
#include
#include
#include
#include
using namespace std;
int n, m;
int main()
{
cin >> n >> m;
queue<int> q;
for (int i = 1; i <= n; i ++ ) q.push(i);
while (m -- )
{
int a;
cin >> a;
a %= q.size();
for (int i = 0; i < a; i ++ )
{
q.push(q.front());
q.pop();
}
cout << q.front() << ' ';
q.pop();
}
return 0;
}
题目链接
算法思路:
由题意:ai - a(i-1) = x(i - 1) % k
(这里的i、i-1
是指下标),又因为1 <= n <= 1000
,所以直接可以枚举k
的取值即可。对于每一个k
的取值,比如k = 3
的时候,有
x0 = a1
x1 = a2 - a1
x2 = a3 - a2
x0 = a4 - a3
x1 = a5 - a4
a1
是否等于a4 - a3
,a2 - a1
是否等于a5 - a4
即可。#include
#include
#include
using namespace std;
const int N = 1010;
int n;
int a[N];
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
vector<int> res;
for (int k = 1; k <= n; k ++ )
{
bool is_match = true;
for (int i = k + 1; i <= n; i ++ )
if (a[i] - a[i - 1] != a[i - k] - a[i - k - 1])
{
is_match = false;
break;
}
if (is_match) res.push_back(k);
}
cout << res.size() << endl;
for (auto k : res)
cout << k << ' ';
cout << endl;
return 0;
}
题目链接
这个题一点都不难,但是要注意逻辑!!!逻辑一定要清晰!!!
class Solution {
public:
string digitSum(string s, int k)
{
int len = s.size();
if (len <= k) return s;
string sb = s, tmp;
while (sb.size() > k)
{
tmp = "";
for (int i = 0; i < sb.size(); i += k) // 这里i的变化!
{
int sum = 0, index = i;
while (index < sb.size() && index < i + k) // 这里的两个判断条件!
{
sum += sb[index] - '0';
index ++ ;
}
tmp += to_string(sum);
}
sb = tmp;
}
return sb;
}
};
题目链接
算法思路
将每一个数出现的次数放进哈希表中,然后分类讨论:
class Solution {
public:
int minimumRounds(vector<int>& tasks)
{
unordered_map<int, int> cnt;
for (auto x : tasks) cnt[x] ++ ;
int ans = 0;
for (auto c : cnt)
{
int x = c.second;
if (x == 1) return -1;
else if (x == 2) ans ++ ;
else if (x % 3 == 0) ans += x / 3;
else ans += x / 3 + 1;
}
return ans;
}
};
题目链接
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int maxTrailingZeros(vector<vector<int>>& grid)
{
// 总体思路:枚举四种拐角路径->[左上,左下,右上,右下]
// 这里由于是求路径中尾随0的的最大个数,因此肯定是路径某个方向取最长的结果(多了某个格子不影响)
// 我们要找尾随0的个数必须要10因子,必然可以分解为2与5因子,[2,5]的对数就是尾随0个数->min(2的个数,5的个数)
int m = grid.size(), n = grid[0].size();
// 创建四个二维数组分别代表:grid[i - 1][j - 1](含)左边的2,5因子总个数;grid[i][j](含)上边的2,5因子总个数
// r2、c2分别代表每一行、每一列中2的因子的数量,r5、c5同理
int r2[m + 1][n + 1], r5[m + 1][n + 1], c2[m + 1][n + 1], c5[m +1][n + 1];
// 默认左边界和上边界的为r2[i][0]=c2[0][j]=r5[i][0]=c5[0][j]=0
memset(r2, 0, sizeof r2), memset(r5, 0, sizeof r5);
memset(c2, 0, sizeof c2), memset(c5, 0, sizeof c5);
// 遍历每个格子完善r2,r5,c2,c5
for (int i = 1; i <= m; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
int cur = grid[i - 1][j - 1];
int two = 0, five = 0;
while (cur % 2 == 0) two ++ , cur /= 2; // 求出cur中2的因数个数
while (cur % 5 == 0) five ++ , cur /= 5; // 求出cur中5的因数个数
// 求前缀和
r2[i][j] = r2[i][j - 1] + two;
r5[i][j] = r5[i][j - 1] + five;
c2[i][j] = c2[i - 1][j] + two;
c5[i][j] = c5[i - 1][j] + five;
}
}
int ans = 0;
// 遍历四种拐弯方向(其余的都可以进行等价)
for (int i = 1; i <= m; i ++ )
{
for (int j = 1; j <= n; j ++ )
{
// grid[i-1][j-1]为拐弯的格子,总体计算方法就是横竖的2或者5因子个数相加,注意避免重叠
// 左边向右出发,然后向上走
ans = max(ans, min(r2[i][j] + c2[i-1][j], r5[i][j] + c5[i - 1][j])); // 注意这里的 -1 是为了避免重复
// 左边向右出发,然后向下走
ans = max(ans, min(r2[i][j] + c2[m][j] - c2[i][j], r5[i][j] + c5[m][j] - c5[i][j]));
// 右边向左出发,然后向上走
ans = max(ans, min(r2[i][n] - r2[i][j] + c2[i][j], r5[i][n] - r5[i][j] + c5[i][j]));
// 右边向左出发,然后向下走
ans = max(ans, min(r2[i][n] - r2[i][j] + c2[m][j] - c2[i - 1][j], r5[i][n] - r5[i][j] + c5[m][j] - c5[i - 1][j]));
}
}
return ans;
}
int main()
{
vector<vector<int>> grid = {{23,17,15,3,20}, {8,1,20,27,11}, {9,4,6,2,21}, {40,9,1,10,6}, {22,7,4,5,3}};
int res = maxTrailingZeros(grid);
cout << res << endl;
return 0;
}
题目链接
class Solution {
public:
int findClosestNumber(vector<int>& nums)
{
int res = 0, min = 1e9;
for (auto c : nums)
{
int x = abs(c);
if (x < min) {
res = c;
min = x;
}
else if (x == min)
if (c > 0)
res = c;
}
return res;
}
};
题目链接
class Solution {
public:
long long waysToBuyPensPencils(int total, int cost1, int cost2)
{
long long sum = 0;
long long cost1Num = total / cost1;
for (int i = 0; i <= cost1Num; i ++ )
{
int max = 1;
if (total - cost1 * i >= cost2)
max += (total - cost1 * i) / cost2;
sum += max;
}
return sum;
}
};
题目链接
逻辑要清晰!!!
class ATM {
private:
long cnt[5]; // 存储不同货币值的个数,int可能会越界
int nums[5] = {20, 50, 100, 200, 500};
public:
ATM() {
memset(cnt, 0, sizeof cnt);
}
void deposit(vector<int> banknotesCount) {
for (int i = 0; i < 5; i++)
cnt[i] += banknotesCount[i];
}
vector<int> withdraw(int amount) {
vector<int> res(5, 0);
for (int i = 4; i >= 0; i--) {
if (amount >= nums[i] && cnt[i] > 0) {
int num = (amount / nums[i] > cnt[i]) ? (int) cnt[i] : (amount / nums[i]);
res[i] = num;
amount -= nums[i] * num;
}
}
// 若果能取出来
if (!amount) {
// 更新ATM中的钱
for (int i = 0; i < 5; i++)
cnt[i] -= res[i];
return res;
}
return {-1};
}
};
题目链接
没有通过的案例:100 100
我的code:
#include
using namespace std;
const int N = 110;
int cnt[N];
int main()
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cnt[i] ++ ;
int res = 0;
for (int i = 1; i <= 100; i ++ )
{
if (i % m == 0)
{
int j = i + 1;
while (cnt[j]) j ++ ;
cnt[j] ++ ;
}
if (cnt[i] == 0)
{
res = i - 1;
break;
}
}
cout << res << endl;
return 0;
}
正确code:
#include
using namespace std;
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1;; i ++ )
{
if (!n)
{
cout << i - 1;
break;
}
n -- ;
if (i % m == 0) n ++ ;
}
return 0;
}
题目链接
可以发现规律,不管初始时x位于哪个位置,都是6次变换为一个周期。
#include
using namespace std;
int main()
{
int n, x;
cin >> n >> x;
string state = "012";
n %= 6;
for (int i = 1; i <= n; i ++)
{
if (i % 2) swap(state[0], state[1]);
else swap(state[1], state[2]);
}
cout << state[x] << endl;
return 0;
}
题目链接
算法思路
首先,由题意bi = b(i+1) 或者 bi + 1 = b(i +1)
可知b
是一个单调递增序列。又若bi = bj
,那么bi ~ bj
全相等。
所以,由上面分析可知,对于每一段(第一段除外)的值都有两种选择,要么和前一段相等,要么比前一段多1
。而对于第一段,因为b1 = 0
,故只有一种选择,即全0
。若总共有m
段,那么数组b
就会有2^m - 1
种。
方法一:区间合并
如果两个区间有交集,那么这两个区间内所有的的值必相等。合并后有几个区间,m
的值就知道了。
这个代码会TLE
#include
#include
#include
#include
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 200010, MOD = 998244353;
int n;
PII q[N];
int main()
{
scanf("%d", &n);
unordered_map<int, int> L, R; // 分别表示每个数出现的最左边和最右边的位置
for (int i = 1; i <= n; i ++ )
{
int a;
scanf("%d", &a);
R[a] = i;
if (!L.count(a)) L[a] = i; // 如果第一次出现,更新下最左边的位置
}
// 把每个元素放进区间里面
int m = 0; // 表示区间的数量
for (auto& [k, v] : L) q[m ++ ] = {L[k], R[k]};
// 区间合并
sort(q, q + m);
int cnt = 0; // 统计区间数量
int st = -1, ed = -1;
for (int i = 0; i < m; i ++ )
if (q[i].x <= ed) ed = max(ed, q[i].y);
else
{
cnt ++ ;
st = q[i].x, ed = q[i].y;
}
int res = 1;
for (int i = 0; i < cnt - 1; i ++ )
res = res * 2 % MOD;
printf("%d\n", res);
return 0;
}
纠正:初始化哈希表的大小,见代码21行
#include
#include
#include
#include
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 200010, MOD = 998244353;
int n;
PII q[N];
int main()
{
scanf("%d", &n);
unordered_map<int, int> L(300000), R(300000); // 分别表示每个数出现的最左边和最右边的位置
for (int i = 1; i <= n; i ++ )
{
int a;
scanf("%d", &a);
R[a] = i;
if (!L.count(a)) L[a] = i; // 如果第一次出现,更新下最左边的位置
}
// 把每个元素放进区间里面
int m = 0; // 表示区间的数量
for (auto& [k, v] : L) q[m ++ ] = {L[k], R[k]};
// 区间合并
sort(q, q + m);
int cnt = 0; // 统计区间数量
int st = -1, ed = -1;
for (int i = 0; i < m; i ++ )
if (q[i].x <= ed) ed = max(ed, q[i].y);
else
{
cnt ++ ;
st = q[i].x, ed = q[i].y;
}
int res = 1;
for (int i = 0; i < cnt - 1; i ++ )
res = res * 2 % MOD;
printf("%d\n", res);
return 0;
}
题目链接
class Solution {
public:
int countPrefixes(vector<string>& words, string s) {
int res = 0;
for (auto str : words)
{
if (str.size() > s.size())
continue;
int len = str.size(), i = 0;
while (i < len)
{
if (str[i] == s[i])
i ++ ;
else break;
}
if (i == len) res ++ ;
}
return res;
}
};
题目链接
前缀和,注意数据要 long long
class Solution {
public:
int minimumAverageDifference(vector<int>& nums)
{
int n = nums.size();
vector<long long> s(n + 1, 0);
for (int i = 1; i <= n; i ++ ) // 处理前缀和
s[i] = s[i - 1] + nums[i - 1];
long long res = 10e9;
int index = 0;
for (int i = 1; i <= n; i ++ )
{
long long left = s[i] / i;
long long right;
if (n == i)
right = 0;
else
right = (s[n] - s[i]) / (n - i);
if (abs(left - right) < res)
{
res = abs(left - right);
index = i - 1;
}
}
return index;
}
};
题目链接
模拟+bfs
class Solution {
public:
int dx[4] = {0, 1, 0, -1}, dy[4] = {-1, 0, 1, 0};
int countUnguarded(int m, int n, vector<vector<int>> &guards, vector<vector<int>> &walls) {
char grid[m][n];
memset(grid, 'N', sizeof grid);
for (auto guard: guards)
grid[guard[0]][guard[1]] = 'G';
for (auto wall: walls)
grid[wall[0]][wall[1]] = 'W';
for (auto guard: guards) {
int x = guard[0], y = guard[1];
for (int i = 0; i < 4; i++) {
int a = x + dx[i], b = y + dy[i];
while (a >= 0 && a < m && b >= 0 && b < n && grid[a][b] != 'G' && grid[a][b] != 'W') {
grid[a][b] = 'I';
// 继续朝该方向前进
a += dx[i];
b += dy[i];
}
}
}
int res = 0;
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++)
if (grid[i][j] == 'N')
res++;
return res;
}
};
题目链接
二分+bfs
注: C++11 Lambda表达式(匿名函数)
static const int dirs[4][2] = {{-1, 0},
{1, 0},
{0, -1},
{0, 1}};
class Solution {
bool check(vector<vector<int>> &grid, int t) {
int m = grid.size(), n = grid[0].size();
bool fire[m][n]; // 记录着过火的位置
memset(fire, 0, sizeof fire);
vector<pair<int, int>> f; // f记录每分钟里面火能蔓延到的位置
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
fire[i][j] = true;
f.emplace_back(i, j);
}
}
// 计算 1 分钟后的火势情况,对f中的每一个火的坐标做一次bfs
// 这个是 C++11 Lambda表达式(匿名函数),具体解释看代码上面的链接
auto spread_fire = [&]() -> void {
vector<pair<int, int>> nf; // nf中只保留了新的扩展的火的坐标,原来的坐标丢弃了
for (auto &[i, j]: f)
for (auto &d: dirs) {
int x = i + d[0], y = j + d[1];
if (0 <= x && x < m && 0 <= y && y < n && !fire[x][y] && grid[x][y] != 2) {
fire[x][y] = true;
nf.emplace_back(x, y);
}
}
f = move(nf); // 不保留nf中的元素,将nf中的元素移到f中
};
while (t-- && !f.empty()) spread_fire(); // 扩充至多 t 分钟的火势; f.empty == 0 表示没有新的坐标可以燃火
if (fire[0][0]) return false; // 起点着火
bool vis[m][n]; // 记录人走过的位置
memset(vis, 0, sizeof vis);
vis[0][0] = true;
vector<pair<int, int>> q; // q记录人每分钟能走到的位置
q.emplace_back(0, 0);
// 停留t分钟后,人开始移动,同时火每分钟也会蔓延到相邻位置
// 对人每分钟能走到的地方bfs,看能否走到安全屋
while (!q.empty()) {
vector<pair<int, int>> nq; // 记录人在每一分钟能走到的地方
for (auto &[i, j]: q)
if (!fire[i][j]) // 如果该处没有着火
for (auto &d: dirs) {
int x = i + d[0], y = j + d[1];
if (0 <= x && x < m && 0 <= y && y < n && !fire[x][y] && !vis[x][y] && grid[x][y] != 2) {
if (x == m - 1 && y == n - 1) return true; // 暂时安全
vis[x][y] = true;
nq.emplace_back(x, y);
}
}
q = move(nq);
spread_fire(); // 扩充1分钟火势
}
return false;
}
public:
int maximumMinutes(vector<vector<int>> &grid) {
int m = grid.size(), n = grid[0].size();
int left = -1, right = m * n;
// 二分寻找最大停留分钟数
while (left < right) {
int mid = (left + right + 1) / 2;
if (check(grid, mid)) left = mid;
else right = mid - 1;
}
return left < m * n ? left : 1e9;
}
};
题目链接
#include
using namespace std;
const int N = 2010;
int n, k;
int main()
{
cin >> n >> k;
int count = 0;
for (int i = 0; i < n; i ++ )
{
int x;
cin >> x;
if (x + k <= 5) count ++ ;
}
cout << count / 3 << endl;
return 0;
}
题目链接
算法思路
分类讨论:
max
即可。#include
#include
#include
using namespace std;
const int INF = 1e8;
int main()
{
int n;
scanf("%d", &n);
int sum = 0;
int a = -INF, b = INF; // a存储最大负奇数,b存储最小正奇数
while (n -- )
{
int x;
scanf("%d", &x);
if (x > 0) sum += x; // 所有正数相加
if (x < 0 && x % 2) a = max(a, x);
if (x > 0 && x % 2) b = min(b, x);
}
if (sum % 2) printf("%d\n", sum);
else printf("%d\n", max(sum + a, sum - b));
return 0;
}
题目链接
算法思路
因为每个不连通的连通块相互独立,分别求每个连通块的方案数,然后求乘积。
又有要使一条边上两个端点的和为奇数,必须是奇数 + 偶数
。所以对于每一个连通图,可以将所有奇数、偶数分别放在不同的集合中,然后连线可以构成一个二分图。所以如果这个图有方案,那么这个图肯定是二分图;反之,如果这个图是二分图,那么它一定存在解。
对于一个连通块的方案数,若这个连通块构成的图是二分图,那么由二分图的性质,对任意一个点,只要其确定了属于哪一个集合,那么这个图中剩余点属于的集合就确定了。假设有左边集合有x
个元素,右边集合有y
个元素,且由题知所有数仅为1、2、3
中之一:
2
种取法,每个偶数仅有1
种取法,那么这种方案总共有2^x + 1^y = 2^x
种取法;2^y
种取法;综上,若一个图是二分图,边总有2^x + 2^y
种取法,其中x
和y
分别为图中奇数和偶数的个数。
#include
#include
#include
using namespace std;
typedef long long LL;
const int N = 300010, M = N * 2, MOD = 998244353;
int n, m;
int h[N], e[M], ne[M], idx;
int col[N]; // 染色
int s1, s2; // 集合点数
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int pow2(int k)
{
int res = 1;
while (k -- ) res = res * 2 % MOD;
return res;
}
bool dfs(int u, int c)
{
col[u] = c;
if (c == 1) s1 ++ ;
else s2 ++ ;
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (col[j] && col[j] != 3 - c) return false;
if (!col[j] && !dfs(j, 3 - c)) return false;
}
return true;
}
int main()
{
int T;
scanf("%d", &T);
while (T -- )
{
scanf("%d%d", &n, &m);
/* 这里不能全部初始化,会TLE */
/* 每个字节为4,本题只用到了 n + 1 个点 */
memset(h, -1, (n + 1) * 4);
memset(col, 0, (n + 1) * 4);
idx = 0;
// 读入边
while (m -- )
{
int a, b;
scanf("%d%d", &a, &b);
add (a, b), add(b, a);
}
int res = 1;
for (int i = 1; i <= n; i ++ ) // 枚举所有连通块
if (!col[i]) // 当前没有没染色,说明是一个新的连通块
{
s1 = s2 = 0;
if (dfs(i, 1))
res = (LL)res * (pow2(s1) + pow2(s2)) % MOD;
else
{
res = 0;
break;
}
}
printf("%d\n", res);
}
return 0;
}