AtCoder Beginner Contest 227(A-F)
知识点整理:
题号 | 知识点 | 备注 |
---|---|---|
A | 模拟,数学 | |
B | 打表法 | |
C | 数学 | |
D | 二分,贪心 | |
E | 字符串,DP | 建议顺便做下leetcode 777 |
F | DP | 好题, 经典题的变形 |
G | 数论 | Poj2992, 数据范围加大 |
H | 图论,欧拉路,网络流 |
本次比赛大概比之前的ABC难一个字母, C相当于D,D相当于E这样子.
简单题
A - Last Card
N个人站成一圈, 从A号人开始依次发卡片,问第K张卡片在谁那里
看到数据范围只有1000, 直接模拟发卡片过程. 懒得推什么模值
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int n, k, a;
cin >> n >> k >> a;
int start = a;
for (int i = 1; i < k; i ++) {
start += 1;
if (start > n) start = 1;
}
cout << start << endl;
return 0;
}
B - KEYENCE building
给你 KEYENCE大楼的面积公式 4 a b + 3 a + 3 b . 4ab+3a+3b. 4ab+3a+3b., 以及一些同学猜测的值, 问有多少人一定猜的是错的?
考虑到 a , b a,b a,b很小,直接打表把1000以内所有的算出来,装到set里面
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
set<int> s;
int n, a;
void init() {
for (int a = 1; a <= 145; a++) {
for (int b = 1; b <= 145; b++) {
s.insert(4*a*b+3*a+3*b);
}
}
}
int main() {
init();
cin >> n;
int res = 0;
for (int i = 1; i <= n; i++) {
cin >> a;
if (s.find(a) == s.end()) res++;
}
cout << res << endl;
return 0;
}
C - ABC conjecture
问有多少个不下降的三元组满足 A B C ≤ N ABC \leq N ABC≤N?
枚举 A A A从1到 N 3 \sqrt[3]{N} 3N, 再枚举 B B B, 统计 C C C有多少种情况
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n;
int main() {
cin >> n;
ll res = 0;
for (ll a = 1; a*a*a <= n; a++)
for (ll b = a; a*b*b <= n; b++)
res += n/a/b-b+1;
cout << res << endl;
return 0;
}
中等题
D - Project Planning
有 N N N个部门,每个部门有 A i A_i Ai个人, 现在需要举办活动, 每个活动抽 K K K个部门, 每个部门抽一个人, 问最多能举办多少个这样的活动?
这道题我一开始想的是排序后二分, 但是发现写不出来, 看了官方题解茅塞顿开
二分答案 P P P, 可以推出一个结论: 令 s u m sum sum 表示数组中 m i n ( A i , P ) min(A_i, P) min(Ai,P)的和, 如果 P ∗ K > s u m P * K > sum P∗K>sum, 则一定可以组织 P P P个活动, 否则不能
可以这样理解, 每次从数组中选择 K K K个数减1, 减 P P P次, 则数组任何一个数最多不能被减超过 P P P次, 所以只要 s u m sum sum比 P ∗ K P * K P∗K.大, 就确保了这 P P P 次操作都有的减. 方案是每次取数组中前 K K K大的数, 只要都比 1 1 1大即可
每次计算 s u m sum sum的复杂度是 O ( n ) O(n) O(n), 二分范围不超过64次, 时间复杂度约为 O ( n l o g M a x ( a i ) ) O(nlogMax(a_i)) O(nlogMax(ai)) 肯定可过
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, k;
ll a[N];
bool check(ll p) {
ll sum = 0;
for (int i = 1; i <= n; i++) sum += min(a[i], p);
return p*k > sum;
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> a[i];
ll l = 0, r = 1e18 / k; // 避免溢出
while (l < r) {
ll mid = (l+r+1ll) >> 1;
// P*K > sum
if (check(mid)) r = mid-1;
else l = mid;
}
cout << l << endl;
return 0;
}
E - Swap
题意:
给你一个字符串由K
, E
, Y
组成, 允许交换相邻的字符
K
K
K次, 问能形成多少种不同的字符串?
题解:
我跟答案一样, 考虑的问题是:
给你两个字符串 A A A, B B B, 问 A A A 通过几次相邻字符的交换能变成 B B B?
这个问题临场死活没想明白, 看leetcode有一个类似题, 但是套不上去, 随即放弃
看答案, 这个问题真的让我五体投地, 可以用dp来做:
d p [ i ] [ x ] [ e ] [ y ] dp[i][x][e][y] dp[i][x][e][y] 表示 B B B字符串的前 i i i位, 有 e e e个E, y y y个Y, 经过 x x x次操作后, 能与 A A A串的前 i − e − y i-e-y i−e−y个K, e e e个E, y y y个Y相对顺序一致的方案数
有点像这样
A: KKKKKK YYYYYYY E ........
B: KYE.................................
假设有一个B串是KYE, 那么B串的KYE和A串第一个k 第一个y 第一个E连线 要求是没有交叉的
这个转移是逆向的, 我们看当前状态 d p [ i ] [ x ] [ e ] [ y ] dp[i][x][e][y] dp[i][x][e][y] 这么多种字符串对哪些 d p [ i + 1 ] dp[i+1] dp[i+1]状态有贡献:
- 在这种状态下的所有串后面加个K: e,y两个维度不变, 看x变化了多少. 我们需要把这个字符K往左移, 移到哪里呢? 移到上面图中不出现交叉线即可, 那么我们看A串中第i-e-y+1个K在哪, 然后看有多少个e和y和这条线交叉了, 就需要把这个K往左挪几格, 记为cnt, 那么 d p [ i + 1 ] [ x + c n t ] [ e ] [ y ] + = d p [ i ] [ x ] [ e ] [ y ] dp[i+1][x+cnt][e][y] += dp[i][x][e][y] dp[i+1][x+cnt][e][y]+=dp[i][x][e][y]
- 另外两个字母同理, 分别贡献到 d p [ i + 1 ] [ e + 1 ] [ y ] [ c + c n t ] , d p [ i + 1 ] [ e ] [ y + 1 ] [ c + c n t ] dp[i + 1][e + 1][y][c + cnt] , dp[i + 1][e][y + 1][c + cnt] dp[i+1][e+1][y][c+cnt],dp[i+1][e][y+1][c+cnt]
最终目标是B串的前n位经过小于等于k次操作后形成一堆竖条, 没有交点, 那么答案就是 ∑ j = 0 k d p [ n ] [ j ] [ e ] [ y ] \sum _{j=0}^kdp[n][j][e][y] ∑j=0kdp[n][j][e][y], 看了下大佬的代码, 总的时间复杂度为 O ( n 5 ) O(n^5) O(n5)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 32;
const int CN2 = N * (N - 1) / 2;
string s;
int K;
ll dp[N][N][N][CN2];
int main() {
cin >> s >> K;
int n = s.size();
K = min(K, n * (n - 1) / 2);
vector<int> kPos, ePos, yPos;
for (int i = 0; i < n; i++) {
char ch = s[i];
if (ch == 'K') kPos.push_back(i);
if (ch == 'E') ePos.push_back(i);
if (ch == 'Y') yPos.push_back(i);
}
int kCnt = kPos.size(), eCnt = ePos.size(), yCnt = yPos.size();
dp[0][0][0][0] = 1;
for (int i = 0; i < n; i++) {
for (int e = 0; e <= eCnt; e++) {
for (int y = 0; y <= yCnt; y++) {
int kk = i - e - y;
if (kk < 0 || kk > kCnt) continue;
for (int sw = 0; sw <= K; sw++) {
// 这个状态对后面没贡献
if (!dp[i][e][y][sw]) continue;
// 试着省略?
if (kk < kCnt) {
int moveSteps = 0; // 把i+1位置的K往左交换
for (int l = 0; l < e; l++) if (ePos[l] > kPos[kk]) moveSteps++;
for (int l = 0; l < y; l++) if (yPos[l] > kPos[kk]) moveSteps++;
if (sw + moveSteps <= K) dp[i + 1][e][y][sw + moveSteps] += dp[i][e][y][sw];
}
if (e < eCnt) {
int moveSteps = 0;
for (int l = 0; l < kk; l++) if (kPos[l] > ePos[e]) moveSteps++;
for (int l = 0; l < y; l++) if (yPos[l] > ePos[e]) moveSteps++;
if (sw + moveSteps <= K) dp[i + 1][e + 1][y][sw + moveSteps] += dp[i][e][y][sw];
}
if (y < yCnt) {
int moveSteps = 0;
for (int l = 0; l < kk; l++) if (kPos[l] > yPos[y]) moveSteps++;
for (int l = 0; l < e; l++) if (ePos[l] > yPos[y]) moveSteps++;
if (sw + moveSteps <= K) dp[i + 1][e][y + 1][sw + moveSteps] += dp[i][e][y][sw];
}
}
}
}
}
ll res = 0;
for (int i = 0; i <= K; i++) res+= dp[n][eCnt][yCnt][i];
cout << res << endl;
return 0;
}
F - Treasure Hunting
题意:
矩阵每个格子有个数字, 从左上角走到右下角的路径中, 前 K K K大的和的最小值是多少?
题解
魔改的经典问题, 第
K
K
K大 的数取值一共就只有
N
2
=
900
N^2 = 900
N2=900 种情况, 我们逐一枚举, 分别判断有没有解, 以及第
K
K
K大的数为a[i][j]
时, 前
K
K
K大数的最小和 是多少, 这个就比较好统计了, 不到
a
[
i
]
[
j
]
a[i][j]
a[i][j]的通通不算, 看走到头的时候比他大的数有多少个 总的时间复杂度为
O
(
n
4
)
O(n^4)
O(n4)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 32;
const int CN2 = N * (N - 1) / 2;
ll a[N][N];
int H, W, K;
ll dp[N][N];
ll solve(ll X) {
memset(dp, 0x3f, sizeof(dp));
dp[0][1] = dp[1][0] = 0;
ll cnt = 0;
for (int i = 1; i <= H; i++) {
for (int j = 1; j <= W; j++) {
dp[i][j] = min(dp[i-1][j], dp[i][j-1]);
if (a[i][j] >= X) {
dp[i][j] += a[i][j] - X;
cnt ++;
}
}
}
if (cnt < K) return INT64_MAX; // 比X大的数都不到k个,无解
return dp[H][W] + 1ll*K*X;
}
int main() {
cin >> H >> W >> K;
for (int i = 1; i <= H; i++) for (int j = 1; j <= W; j++) cin >> a[i][j];
ll res = INT64_MAX;
for (int i = 1; i <= H; i++)
for (int j = 1; j <= W; j++)
res = min(res, solve(a[i][j]));
cout << res << endl;
return 0;
}
高级题
G - Divisors of Binomial Coefficient
题意:
求 C n k C_n^k Cnk 有多少个因子
H - Eat Them All
题意:
给你一个3*3的矩阵, 每个矩阵有若干个豆子, 从左上角开始吃豆子,一次只能吃一个, 问有没有一种方案, 使得恰好吃完所有的豆子又回到左上角
这两个暂时不会