A yogurt sale
题目:
思路:分类讨论
#include <iostream>
using namespace std;
void solve() {
int n, a, b;
cin >> n >> a >> b;
if(a * 2 < b) {
cout << n * a << endl;
} else {
if(n % 2) cout << b * (n / 2) + a << endl;
else cout << b * (n / 2) << endl;
}
}
int main() {
int t;
cin >> t;
while(t -- ) {
solve();
}
return 0;
}
B progressive square
题目:
思路:先按列(或者行)放置生成矩阵判断是否合法,若合法再按行(列)检查,如果两个判断都通过则数据合法
#include <iostream>
#include <map>
using namespace std;
const int N = 510;
int g[N][N];
void solve() {
map<int, int> ma;
int n, c, d;
cin >> n >> c >> d;
int start = 111111111;
for(int i = 1; i <= n; i ++ ) {
for(int j = 1; j <= n; j ++ ) {
int x;
cin >> x;
start = min(start, x);
ma[x] ++;
}
}
ma[start] --;
g[1][1] = start;
for(int i = 2; i <= n; i ++ ) {
int a = g[1][i - 1] + d;
if(!ma[a]) {
cout << "NO" << endl;
return;
} g[1][i] = a;//处理第一行
ma[a] --;
}
for(int i = 1; i <= n; i ++ ) {
for(int j = 2; j <= n; j ++ ) {
int a = g[j - 1][i] + c;
if(!ma[a]) {
cout << "NO" << endl;
return;
}
g[j][i] = a;
ma[a] --;
}
}
for(int i = 1; i <= n; i ++ ) {
for(int j = 2; j <= n; j ++ ) {
if(g[i][j] != g[i][j - 1] + d) {
cout << "NO" << endl;
return;
}
}
}
cout << "YES" << endl;
}
int main() {
int t;
cin >> t;
while(t -- ) {
solve();
}
return 0;
}
C inhabitant of the deep sea
题目:
思路:用if条件判断语句将所有情况不重不漏的分类,再分别讨论,笔者这里是一第一个战舰和最后一个战舰耐久度的高低作为分类条件
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
typedef long long LL;
LL k;
int n;
long long d[N];
void solve() {
cin >> n >> k;
for(int i = 1; i <= n; i ++ ) cin >> d[i];
int ans = 0;
int i, j;
string pre = "head";
for(i = 1, j = n; i < j; ) {
if(pre == "head") {
if(d[i] <= d[j]) {
if(k >= d[i] * 2 - 1) {
ans ++;
d[j] -= d[i] - 1;
k -= d[i] * 2 - 1;
i ++;
pre = "tail";
} else {
cout << ans << endl;
return;
}
} else {
if(k >= d[j] * 2) {
ans ++;
d[i] -= d[j];
k -= d[j] * 2;
j --;
pre = "head";
} else {
cout << ans << endl;
return;
}
}
} else if(pre == "tail") {
if(d[j] <= d[i]) {
if(k >= d[j] * 2 - 1) {
ans ++;
d[i] -= d[j] - 1;
k -= d[j] * 2 - 1;
j --;
pre = "head";
} else {
cout << ans << endl;
return;
}
} else {
if(k >= d[i] * 2) {
ans ++;
d[j] -= d[i];
k -= d[i] * 2;
i ++;
pre = "tail";
} else {
cout << ans << endl;
return;
}
}
}
}
if(k >= d[i]) ans ++;
cout << ans << endl;
}
int main() {
int t;
cin >> t;
while(t -- ) {
solve();
}
return 0;
}
上述方法过于繁琐,其实这个问题转化一下就变成了前面的战舰会受到 k / 2上取整次进攻,后面的战舰会受到 k / 2下取整次进攻 预处理出来前缀和与后缀和,二分找到前面进攻和后面进攻可以击落多少战舰相加即可,注意如果相加后结果大于n输出 n即可
代码
#include <iostream>
using namespace std;
const int N = 2e5 + 10;
typedef long long LL;
LL k;
int n;
long long d[N];
long long s1[N];
long long s2[N];
void solve() {
cin >> n >> k;
for(int i = 1; i <= n; i ++ ) cin >> d[i];
for(int i = 1; i <= n; i ++ ) s1[i] = s1[i - 1] + d[i];
if(k >= s1[n]) {
cout << n << endl;
return;
}
for(int i = 1; i <= n; i ++ ) s2[i] = s2[i - 1] + d[n + 1 - i];
long long k1 = k + 1 >> 1, k2 = k >> 1;
int l = 0, r = n;
int ans = 0;
while(l < r) {
int mid = l + r + 1 >> 1;
if(s1[mid] <= k1) l = mid;
else r = mid - 1;
}
ans += l;
l = 0, r = n;
while(l < r) {
int mid = l + r + 1 >> 1;
if(s2[mid] <= k2) l = mid;
else r = mid - 1;
}
ans += l;
cout << ans << endl;
}
int main() {
int t;
cin >> t;
while(t -- ) {
solve();
}
return 0;
}
D inaccurate subsequence search
题目:
思路:该题目意为在a中有多少长度为m的子段使得这个子段中至少有k个元素与b相同,这个问题类似于,滑动窗口,只要动态的维护一段长度为m的区间,维护时判断是否满足题意,若满足则ans ++。
问题1:本身是想用队列的长度来当做判断区间是否合法的条件if(q.size() > k) ans ++ 但发现这样无法处理相同元素 例如:先用map储存b中所有的元素,如果下一个元素在map中则进队(例如b中有1, 1。由于在维护时只要是在map中的元素都会对答案产生贡献,因此窗口中不会限制有贡献元素的数量,所以窗口中可能会出现有1, 1, 1此时只能算两个1)
解决:再开一个map1维护区间中元素数量,用cnt表示区间中与b中元素相同的个数,如果在维护时发现map1中的元素数量大于本身map中元素数量,则在入队时cnt不再自增,同理,如果数量足够大,在弹出区间时cnt也不用自减
#include <iostream>
#include <unordered_map>
using namespace std;
const int N = 2e5 + 10;
int n, m, k;
int a[N], b[N];
void solve() {
cin >> n >> m >> k;
unordered_map<int, int> ma;//储存b数组中元素以及各种元素的数量
unordered_map<int, int> ma1;//储存当前长度为m的窗口中属于b的元素,以及数量
int cnt = 0;
int ans = 0;
for(int i = 1; i <= n; i ++ ) cin >> a[i];
for(int j = 1; j <= m; j ++ ) {
cin >> b[j];
ma[b[j]] ++;
}
for(int i = 1; i <= n; i ++ ) {
int x = a[i];
if(ma[x]) {
ma1[x] ++;
if(ma1[x] <= ma[x]) cnt ++;
}
if(i - m > 0 && ma[a[i - m]]) {
ma1[a[i - m]] --;
if(ma1[a[i - m]] < ma[a[i - m]]) cnt --;
}
if(i - m >= 0 && cnt >= k) ans ++;
}
cout << ans << endl;
}
int main() {
int t;
cin >> t;
while(t -- ) {
solve();
}
return 0;
}
这里代码用的unordered map容易被hack最差时间复杂度可能会到o(n^2)
E long inversions
题目:
思路:暴力枚举可能的长度 + 贪心差分判断这个长度是否可行
#include <iostream>
#include <cstring>
using namespace std;
const int N = 5010;
char str[N];
int n;
int difference[N];
int backup[N];
int a[N];
int sum[N];
bool check(int len) {
memcpy(backup, difference, sizeof difference);
memset(sum, 0, sizeof sum);
for(int i = 1; i <= n - len + 1; i ++ ) {
sum[i] = sum[i - 1] + backup[i];
if(!(sum[i] % 2)) {
sum[i] += 1;
backup[i] += 1;
backup[i + len] -= 1;
}
}
memset(sum, 0, sizeof sum);
for(int i = 1; i <= n; i ++ ) {
sum[i] = sum[i - 1] + backup[i];
if(!(sum[i] % 2)) return false;
}
return true;
}
void solve() {
memset(difference, 0, sizeof difference);
cin >> n;
for(int i = 1; i <= n; i ++ ) {
cin >> str[i];
if(str[i] == '1') a[i] = 1;
else a[i] = 0;
}
//for(int i = 1; i <= n; i ++ ) cout << a[i] << " ";
for(int i = 1; i <= n; i ++ ) difference[i] = a[i] - a[i - 1];
for(int k = n; k >= 1; k -- ) {
if(check(k)) {
cout << k << endl;
return;
}
}
}
int main() {
int t;
cin >> t;
while(t -- ) {
solve();
}
return 0;
}
F unfair game
题目
思路:可以发现,只有4会影响从右向左数第三位数位中1的数量,因此这个可以拉出来单独讨论,剩下的第一位数位中1的数量会被1与3影响,第二位数位中1的数量会被2与3影响因此1,2数位可以用动态规划来求dp[i][j][k]表示1,2,3卡片分别有ijk张时bob赢的最大次数,最后的答案是两部分相加
#include <iostream>
using namespace std;
const int N = 210;
int dp[N][N][N];
int p[4];
void solve() {
for(int i = 1; i <= 4; i ++ ) cin >> p[i];
dp[0][0][0] = 0;
cout << dp[p[1]][p[2]][p[3]] + p[4] / 2 << endl;
}
int main() {
int t;
cin >> t;
for(int i = 0; i < N; i ++ ) {
for(int j = 0; j < N; j ++ ) {
for(int k = 0; k < N; k ++ ) {
int prev = 0;
if(i) prev = max(prev, dp[i - 1][j][k]);
if(j) prev = max(prev, dp[i][j - 1][k]);
if(k) prev = max(prev, dp[i][j][k - 1]);
dp[i][j][k] = prev;
if(i == 0 && j == 0 && k == 0) continue;
if(!((i + k) % 2) && !((j + k) % 2)) dp[i][j][k] ++;
}
}
}
while(t -- ) {
solve();
}
return 0;
}
G gcd on a grid
题目
思路: 类似于区间三角形线性dp,这道题每个位置的dp都可以从他正上方的位置的dp与他左方位置的dp转移过来。由于路径必须通过1,1与n,n因此答案肯定是这两点gcd的约数 因此暴力枚举这两点gcd的约数再从大到小的判断这个约数是否满足路径,如果路径上的点都是该约数的倍数即可说明该数为合法解
这里的dp[i][j]类型是bool 表示到该点的路径是否存在一条满足这条路径上的所有点都是这个数的倍数
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 110;
int g[N][N];
int n, m;
bool dp[N][N];
int gcd(int a, int b) {
return b? gcd(b, a % b): a;
}
bool cmp(int a, int b) {
return a > b;
}
void solve() {
cin >> n >> m;
for(int i = 1; i <= n; i ++ ) {
for(int j = 1; j <= m; j ++ ) {
cin >> g[i][j];
}
}
int gc = gcd(g[1][1], g[n][m]);
vector<int> div;
for(int i = 1; i <= gc / i; i ++ ) {
if(gc % i == 0) {
div.push_back(i);
if(gc / i != i)
div.push_back(gc / i);
}
}
sort(div.begin(), div.end(), cmp);
dp[1][1] = true;
for(auto d: div) {
for(int i = 1; i <= n; i ++ ) {
for(int j = 1; j <= m; j ++ ) {
if(i == 1 && j != 1) {
dp[1][j] = dp[1][j - 1] && !(g[i][j] % d);
} else if(j == 1 && i != 1) {
dp[i][1] = dp[i - 1][1] && !(g[i][j] % d);
} else if(j != 1 && i != 1){
dp[i][j] = (dp[i - 1][j] && !(g[i][j] % d)) || (dp[i][j - 1] && !(g[i][j] % d));
}
}
}
if(dp[n][m]) {
cout << d << endl;
return;
}
}
}
int main() {
int t;
cin >> t;
while(t -- ) {
solve();
}
return 0;
}
H the most reckless defence