比赛链接:
https://codeforces.com/gym/104023
A. Dunai
题意:
\(n\) 个队伍获得过冠军,告知每个队伍中的人及对应的位置,现在已知 \(m\) 个选手及它们的位置,问能组成多少个五个人,满足每个位置上有一人且队伍中至少一人拿过冠军。
思路:
答案即每个位置人数的最小值与冠军总数中小的那个值。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n;
cin >> n;
map<string, int> Map;
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= 5; j ++ ){
string s;
cin >> s;
Map[s] = 1;
}
}
int m;
cin >> m;
vector<string> t[6];
for (int i = 1; i <= m; i ++ ){
string s;
int id;
cin >> s >> id;
t[id].push_back(s);
}
vector<int> cnt(6);
for (int i = 1; i <= 5; i ++ ){
for (auto x : t[i]){
if (Map[x]){
cnt[i] ++ ;
}
}
}
int ans = 2000;
for (int i = 1; i <= 5; i ++ ){
ans = min(ans, (int)t[i].size());
}
cout << min(ans, accumulate(cnt.begin(), cnt.end(), 0)) << "\n";
return 0;
}
C. Grass
题意:
给定平面上 \(n\) 个点,选择四个点 \(B,C,D,E\) 以及一个中心点 \(A\),要求四个点与中心点的连线不相交,问是否存在这样的五个点。
思路:
可以发现只要五点不共线就一定满足要求,固定四个点,枚举第五个点,判断是否五点共线(通过斜率可以判断)。
接着选取一个点作为中心点,然后判断它是不是可以作为中心(先选择一个点,判断剩余三个是不是在它与中心的连线上)。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
int n;
cin >> n;
vector<LL> x(n), y(n);
for (int i = 0; i < n; i ++ )
cin >> x[i] >> y[i];
if (n < 5){
cout << "NO\n";
return;
}
vector<int> p = {0, 1, 2, 3, 4};
auto check = [&](){
for (int i = 1; i < 4; i ++ ){
if ((y[p[i]] - y[p[i - 1]]) * (x[p[i + 1]] - x[p[i]]) != (y[p[i + 1]] - y[p[i]]) * (x[p[i]] - x[p[i - 1]])){
return true;
}
}
return false;
};
auto judge = [&](int c){
for (int a = 0; a < 5; a ++ ){
if (a == c) continue;
for (int b = 0; b < 5; b ++ ){
if (b == a || b == c) continue;
int minx = min(x[p[a]], x[p[c]]);
int maxx = max(x[p[a]], x[p[c]]);
int miny = min(y[p[a]], y[p[c]]);
int maxy = max(y[p[a]], y[p[c]]);
if (minx <= x[p[b]] && x[p[b]] <= maxx && miny <= y[p[b]] && y[p[b]] <= maxy && ((y[p[b]] - y[p[a]]) * (x[p[c]] - x[p[b]]) == (y[p[c]] - y[p[b]]) * (x[p[b]] - x[p[a]]))){
return false;
}
}
}
return true;
};
for (int i = 4; i < n; i ++ ){
p[4] = i;
if (check()){
cout << "YES\n";
for (int c = 0; c < 5; c ++ ){
if (judge(c)){
cout << x[p[c]] << " " << y[p[c]] << "\n";
for (int j = 0; j < 5; j ++ ){
if (c == j) continue;
cout << x[p[j]] << " " << y[p[j]] << "\n";
}
return;
}
}
}
}
cout << "NO\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
D. Sternhalma
题意:
19 个格子的六边形跳棋棋盘,上面有若干棋子,有两种操作:
1.移除一个棋子,获得 0 分。
2.某个棋子跳过相邻的一个棋子,移除被跳的那个棋子,获得该位置的得分。
现在已知每个位置的分数,告诉若干个刚开始棋子的布局,求出每个移除所有棋子后的最大得分。
思路:
总共 19 个格子,通过状压将每个状态都表示出来。从没有棋子的状态(即 0,这是逆向的,所有操作反一下)出发进行 \(dfs\)。
有两种转移的状态:
1.直接加上一个棋子,不得分。
2.跳棋,有六种情况,左上到右下,右上到左下,左到右,或者方向反一下。
依次 \(dfs\) 即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
vector<int> w(19);
for (int i = 0; i < 19; i ++ )
cin >> w[i];
vector<int> L = {-1, 0, 1, -1, 3, 4, 5, -1, 7, 8, 9, 10, -1, 12, 13, 14, -1, 16, 17};
vector<int> R = {1, 2, -1, 4, 5, 6, -1, 8, 9, 10, 11, -1, 13, 14, 15, -1, 17, 18, -1};
vector<int> UL = {-1, -1, -1, -1, 0, 1, 2, -1, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14};
vector<int> UR = {-1, -1, -1, 0, 1, 2, -1, 3, 4, 5, 6, -1, 8, 9, 10, 11, 13, 14, 15};
vector<int> DL = {3, 4, 5, 7, 8, 9, 10, -1, 12, 13, 14, 15, -1, 16, 17, 18, -1, -1, -1};
vector<int> DR = {4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, -1, 16, 17, 18, -1, -1, -1, -1};
vector<int> dp((1 << 19) - 1, -1e9);
dp[0] = 0;
function<void(int)> dfs = [&](int st){
auto add = [&](int st, int i, int j, int k){
if (!(st & (1 << i)) && (st & (1 << k))){
if (dp[st] + w[j] > dp[st ^ (1 << i) ^ (1 << j) ^ (1 << k)]){
dp[st ^ (1 << i) ^ (1 << j) ^ (1 << k)] = dp[st] + w[j];
dfs(st ^ (1 << i) ^ (1 << j) ^ (1 << k));
}
}
};
for (int i = 0; i < 19; i ++ ){
if (!(st & (1 << i))){
if (UL[i] != -1 && DR[i] != -1) add(st, UL[i], i, DR[i]);
if (DR[i] != -1 && UL[i] != -1) add(st, DR[i], i, UL[i]);
if (UR[i] != -1 && DL[i] != -1) add(st, UR[i], i, DL[i]);
if (DL[i] != -1 && UR[i] != -1) add(st, DL[i], i, UR[i]);
if (L[i] != -1 && R[i] != -1) add(st, L[i], i, R[i]);
if (R[i] != -1 && L[i] != -1) add(st, R[i], i, L[i]);
if (dp[st] > dp[st ^ (1 << i)]){
dp[st ^ (1 << i)] = dp[st];
dfs(st ^ (1 << i));
}
}
}
};
dfs(0);
int n;
cin >> n;
while(n -- ){
int st = 0;
for (int i = 0; i < 19; i ++ ){
char c;
cin >> c;
if (c == '#') st ^= (1 << i);
}
cout << dp[st] << "\n";
}
return 0;
}
E. Python Will be Faster than C++
题意:
已知 \(n\) 个版本的运行速度,现在要找到第一个比 \(k\) 小的版本。已知后续第 \(i\) 个版本的运行速度为 \(max(2 * a_{i - 1} - a_{i - 2}, 0)\)。
思路:
只有当最后两个版本是递减的时候,才会有答案。可以发现后面的就是一个等差,根据公式求一下即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int n, k;
cin >> n >> k;
vector<int> a(n + 1);
for (int i = 1; i <= n; i ++ )
cin >> a[i];
int d = a[n] - a[n - 1];
if (d >= 0){
cout << "Python will never be faster than C++\n";
}
else{
int t = (a[n] - k) / d;
cout << "Python 3." << abs(t) + n + 1 << " will be faster than C++\n";
}
return 0;
}
G. Grade 2
题意:
给定 \(x\) 和 \(n\) 组 \(L, R\)。求 \(\sum_{k = L}^R [gcd(kx \bigoplus x, x) = 1]\)
思路:
打表可以发现循环节为 \(2^{\lceil log_2x \rceil}\)。暴力跑出前 \(2^{\lceil log_2x \rceil}\) 个,然后直接求解。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
int main(){
ios::sync_with_stdio(false);cin.tie(0);
LL x, n;
cin >> x >> n;
LL T = 1;
while(T < x){
T <<= 1;
}
vector<LL> sum(T + 1);
for (int k = 1; k <= T; k ++ ){
sum[k] = sum[k - 1] + (gcd((k * x) ^ x, x) == 1);
}
auto calc = [&](LL x){
return (x - 1) / T * sum[T] + sum[(x - 1) % T + 1];
};
for (int i = 1; i <= n; i ++ ){
LL L, R;
cin >> L >> R;
cout << calc(R) - calc(L - 1) << "\n";
}
return 0;
}
I. Dragon Bloodline
题意:
一个龙蛋需要 \(n\) 种原料,第 \(i\) 种原料需要 \(a_i\) 个,现在有 \(k\) 种龙,第 \(i\) 种龙有 \(b_i\) 个,当它被指定去生产某种原料后,它可以采集这种原料 \(2^i\) 个,问最多能生产多少龙蛋。
思路:
随着龙蛋数量的增多,所需的原料也增多,考虑二分答案。
按照龙的生产效率从大到小,所需的原料通过优先队列进行排列,当某种原料的需要大于等于龙的生产力的时候,该条龙被分配去生产这种原料显然是最贪的,如果该种原料还需要龙生产,再将它加入队列中。当生产力大于取出来的原料时(堆顶的元素是最大的),直接删除这个值即可,因为没办法避免生产力的浪费了。
注意二分的上限,如果不加限制会爆,可用 int128 或限制上限。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
int n, k;
cin >> n >> k;
vector<LL> a(n);
for (int i = 0; i < n; i ++ )
cin >> a[i];
vector<LL> b(k);
for (int i = 0; i < k; i ++ )
cin >> b[i];
auto check = [&](LL x){
auto c = b;
priority_queue<LL> q;
for (int i = 0; i < n; i ++ )
q.push(a[i] * x);
for (int j = k - 1; j >= 0; j -- ){
while(!q.empty() && c[j]){
LL x = q.top();
q.pop();
LL p = min(c[j], x / (1 << j));
if (p == 0){
c[j] -- ;
}
else{
c[j] -= p;
x -= p * (1 << j);
if (x){
q.push(x);
}
}
}
}
return q.empty();
};
LL L = 0, R = 0;
for (int i = 0; i < k; i ++ ){
R += (1 << i) * b[i];
}
R /= *min_element(a.begin(), a.end());
while(L < R){
LL mid = L + R + 1 >> 1;
if (check(mid)){
L = mid;
}
else{
R = mid - 1;
}
}
cout << L << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}
J. Eat, Sleep, Repeat
题意:
给定 \(n\) 个数,\(k\) 个限制,限制 \(i\) 限制数 \(x_i\) 不能超过 \(y_i\) 个。
\(Pico\) 和 \(FuuFuu\) 轮流选择一个数将它的值减一,前者先手,二者均采用最优策略。当某条限制不满足或者所有数都为 0 时,当前这个人输,问先手胜还是后手胜。
思路:
全部为 0 的可以看成 -1,0 这个限制。
依据所有 \(x\) 不能超过 0 个的限制,将所有数划分为若干个区间。因为这些数不可能会低于 \(x\),同时它们要满足其它限制,所以它们最后的值可以确定。求出这些值的总和,判断奇偶即可。
代码:
#include <bits/stdc++.h>
using namespace std;
using LL = long long;
void solve(){
int n, k;
cin >> n >> k;
vector<int> a(n);
for (int i = 0; i < n; i ++ )
cin >> a[i];
vector<pair<int, int>> lim(k + 1);
map<int, int> Map;
for (int i = 1; i <= k; i ++ ){
cin >> lim[i].first >> lim[i].second;
Map[lim[i].first] = lim[i].second;
}
sort(a.begin(), a.end());
lim[0] = {-1, 0};
sort(lim.begin(), lim.end());
LL ans = 0;
for (int j = k, i = n - 1; j >= 0; j -- ){
if (lim[j].second) continue;
int ti = i, t = lim[j].first + 1;
while(ti >= 0 && a[ti] >= t){
ti -- ;
}
for (int it = ti + 1; it <= i; it ++ ){
while(Map.count(t) && Map[t] == 0){
t ++ ;
}
ans += a[it] - t;
if (Map.count(t)){
Map[t] -- ;
}
}
i = ti;
}
cout << ((ans & 1) ? "Pico" : "FuuFuu") << "\n";
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
int T;
cin >> T;
while(T -- ){
solve();
}
return 0;
}