2024 暑假友谊赛 1
AtCoder abc204_d
一开始想着贪心,试了下wa掉了,然后看着过的人挺多的还是觉得是贪心🤡(试了好几遍
思路:答案最小为sum/2,那么就是找到大于等于sum/2的最小子集和,上dp,f[i][j]表示前i个数中是否存在子集和为j(看数据范围也可以看出是dp的
void solve() {
int n;
cin >> n;
vector<int> ve(n + 1);
int sum = 0;
for (int i = 1; i <= n; ++i) {
cin >> ve[i];
sum += ve[i];
}
vector<vector<int> > f(n + 1, vector<int> (sum + 1));
f[0][0] = 1;
int ans = LLONG_MAX;
for (int i = 1; i <= n; ++i) {
for (int j = ve[i]; j <= sum; ++j) {
f[i][j] = f[i - 1][j] | f[i - 1][j - ve[i]];
}
}
for (int i = 1; i <= sum; ++i) {
if (f[n][i]) {
ans = min(ans, max(i, sum - i));
}
}
cout << ans;
}
AtCoder arc092_a
思路:
可以直接用匈牙利求二分图最大匹配
也可以二分,坐标的范围不大,将红蓝点分别按x坐标存y,从左开始枚举x坐标,用multiset存储当前x左边所有未匹配的红点y坐标,然后枚举当前x坐标下的蓝点的y,二分出最接近y的红点(小于y),将这两点进行匹配
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e5 + 5, mod = 998244353;
struct E {
int x, y;
};
void solve() {
int n;
cin >> n;
vector<E>r(n + 1), b(n + 1);
for (int i = 1; i <= n; ++i) cin >> r[i].x >> r[i].y;
for (int i = 1; i <= n; ++i) cin >> b[i].x >> b[i].y;
vector<vector<int> > ve(n + 1);
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (r[i].x < b[j].x && r[i].y < b[j].y) {
ve[i].push_back(j);
}
}
}
int ans = 0;
vector<int> st(n + 1), to(n + 1);
auto dfs = [&](int u, auto dfs)->bool {
for (auto v:ve[u]) {
if (st[v]) continue;
st[v] = 1;
if (!to[v] || dfs(to[v], dfs)) {
to[v] = u;
return true;
}
}
return false;
};
for (int i = 1; i <= n; ++i) {
std::fill(st.begin(), st.end(), 0);
if (dfs(i, dfs)) ans ++;
}
cout << ans;
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T --) {
solve();
}
return 0;
}
CodeForces 1551D1s
思路:分类讨论n*m的情况,记a为水平,b为垂直
a | a | a | a | a | a |
奇*偶:
有一行只能被a覆盖
因为不管某一列有多少个b,这一列的b总共占偶数个,始终会剩一个,那么至少有一个a来覆盖。其余部分作为偶*偶处理
只需要判断k是否大于等于m/2,然后用m/2个a将第一行覆盖
a | a | ||||
a | a | ||||
偶*偶:
若要使用a,必须2个2个使用,即k为偶数个
首先对于列来说,不管a占用了多少列,最后会剩下偶数列,对b不会造成影响
对于行来说,需要保证剩下的行数为偶数,所以每次需要用2个a占两行
b | ||||
b | ||||
b | ||||
b |
偶*奇:
某一列必须全为b,剩余部分作为偶*偶处理
对于一行来说,不管占用多少个a,最后剩下格子数一定为奇数,有一列只能由b来覆盖,所以就先将第一列全用b覆盖
对于b的数量并没有限制,直接把那一列删去即可
特别要判断下n为1或m为1的情况,只能由a或b覆盖
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e5 + 5, mod = 998244353;
void solve() {
int n, m, k;
cin >> n >> m >> k;
if (n == 1) {
if (k == m / 2) cout << "YES\n";
else cout << "NO\n";
return ;
}
if (n % 2) {
if (k >= m / 2) {
k -= m / 2;
n --;
} else {
cout << "NO\n";
return ;
}
}
if (m == 1) {
if (k == 0) cout << "YES\n";
else cout << "NO\n";
return ;
}
if (m % 2) m --;
if (2 * k <= n * m && k % 2 == 0) {
cout << "YES\n";
} else cout << "NO\n";
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T --) {
solve();
}
return 0;
}
AtCoder abc123_d
也是很暴力的题,稍微想想就能懂的🤡
思路:首先三重循环肯定会暴的,那就先搞个二重把a[i]+b[j]的全部可能求出来,然后与c再进行匹配
这里注意到k其实只有3000,说明在a[i]+b[j]的集合里最多只有最大前k个是有效的,同样c里也是最多有前k个是有效的,那就分别只维护前k个就行了,最后在暴力的两重循环维护前k个就好了
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e5 + 5, mod = 998244353;
void solve() {
int x, y, z, k;
cin >> x >> y >> z >> k;
vector<int> a(x), b(y), c(z);
for (auto &v:a) cin >> v;
for (auto &v:b) cin >> v;
for (auto &v:c) cin >> v;
priority_queue<int, vector<int>, greater<int> > q, qe;
for (int i = 0; i < x; ++i) {
for (int j = 0; j < y; ++j) {
q.push(a[i] + b[j]);
if (q.size() > k) q.pop();
}
}
while (q.size()) {
auto u = q.top();
q.pop();
for (int i = 0; i < z; ++i) {
qe.push(u + c[i]);
if (qe.size() > k) qe.pop();
}
}
vector<int> ans;
while (k --) {
ans.push_back(qe.top());
qe.pop();
}
std::reverse(ans.begin(), ans.end());
for (auto v:ans) cout << v << '\n';
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T --) {
solve();
}
return 0;
}
AtCoder arc078_b
CodeForces 448D
思路:打表看出来每一斜杠的最大值都大于前一斜杠,那就是要求出当前斜杠前的总个数,并且求出最靠后的那一条保证总个数小于k的斜杠,那么第k个数一定在下一斜杠...问题就是nm不同的话求斜杠前的总个数就很麻烦
最后考虑的二分,就比较好写,二分答案,统计小于等于当前数的总个数,枚举其中一个乘数就可以求出另一个乘数的个数
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e5 + 5, mod = 998244353;
void solve() {
int n, m, k;
cin >> n >> m >> k;
int l = 1, r = n * m, ans;
auto check = [=] (int x) {
int cnt = 0;
for (int i = 1; i <= n; ++i) {
cnt += min(x / i, m);
}
return cnt >= k;
};
while (l <= r) {
int mid = l + r >> 1;
if (check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
cout << ans;
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T --) {
solve();
}
return 0;
}
CodeForces 1618F
思路:首先将xy转化为二进制,现在考虑如何将x转换成y
假定二进制ABCD,1表示在末尾加‘1’后翻转,0表示在末尾加‘0’后翻转
进行操作:
1 → 1DCBA
10 → ABCD1
11 → 1ABCD1
0 → DCBA (无前缀零)
01 → 1ABCD (无后缀零)
00 → ABCD (无后缀零)
可以发现,进行这些操作后的二进制数首尾都不会存在0,且可以通过这些操作在首尾加任意个‘1’
可以归纳为四种基础情况,分别为1DCBA、ABCD1、DCBA、ABCD,再分别进行首尾加‘1’与y比较即可
其次是数的范围为1e18,最多有64位,暴力枚举首尾最多放64位即可
void solve() {
int x, y;
cin >> x >> y;
if (x == y) {
cout << "YES\n";
return ;
}
string a, b;
while (x) {
if (x % 2) a.push_back('1');
else a.push_back('0');
x /= 2;
}
while (y) {
if (y % 2) b.push_back('1');
else b.push_back('0');
y /= 2;
}
std::reverse(a.begin(), a.end());
std::reverse(b.begin(), b.end());
vector<string> ve;
// ABCD1
ve.push_back(a + '1');
std::reverse(a.begin(), a.end());
// DCBA1
ve.push_back('1' + a);
std::reverse(a.begin(), a.end());
while (a.size() && a.back() == '0') a.pop_back();
// ABCD(无后缀0)
ve.push_back(a);
std::reverse(a.begin(), a.end());
// DCBA(无前缀0)
ve.push_back(a);
for (int i = 0; i < ve.size(); ++i) {
string l;
for (int j = 0; j <= 64; ++j) {
if (j + ve[i].size() > b.size()) break;
string r;
for (int k = 0; k <= 64; ++k) {
if (j + ve[i].size() + k > b.size()) break;
if (l + ve[i] + r == b) {
cout << "YES\n";
return ;
}
r.push_back('1');
}
l.push_back('1');
}
}
cout << "NO\n";
}
AtCoder abc265_a
思路:暴力枚举xy的个数
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 1e5 + 5, mod = 998244353;
void solve() {
int x, y, n;
cin >> x >> y >> n;
int ans = LLONG_MAX;
for (int i = 0; i <= n; ++i) {
for (int j = 0; j * 3 <= n; ++j) {
if (i + j * 3 == n) {
ans = min(i * x + j * y, ans);
}
}
}
cout << ans;
}
signed main() {
ios::sync_with_stdio(0),cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T --) {
solve();
}
return 0;
}
AtCoder abc207_e
思路:题目说道这是一棵树,那1和n之间只有一条路径,且F和S一人得一半。那就是求所有点到1和n的最短路径,若一个点到1的最短路径小于等于到n的最短路径,那么这个点是被1选择的
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define PII pair<int, int>
const int N = 2e5 + 5, mod = 998244353;
void solve() {
int n;
cin >> n;
vector<vector<int> > ve(n + 1);
for (int i = 1; i < n; ++i) {
int a, b;
cin >> a >> b;
ve[a].push_back(b), ve[b].push_back(a);
}
vector<array<int, 2> > dis(n + 1);
auto bfs = [&] (int u, int op) {
queue<int> q;
vector<int> st(n + 1);
q.push(u);
dis[u][op] = 0;
st[u] = 1;
while (q.size()) {
auto t = q.front();
q.pop();
for (auto v:ve[t]) {
if (!st[v]) {
st[v] = 1;
dis[v][op] = dis[t][op] + 1;
q.push(v);
}
}
}
};
bfs(1, 0), bfs(n, 1);
int cnta = 0, cntb = 0;
for (int i = 1; i <= n; ++i) {
if (dis[i][0] <= dis[i][1]) cnta ++;
else cntb ++;
}
if (cnta > cntb) cout << "Fennec";
else cout << "Snuke";
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}