Educational Codeforces Round 108 (Rated for Div. 2)
Contest Info
Solved | A | B | C | D | E | F |
---|---|---|---|---|---|---|
5 / 6 | O | O | O | O | Ø | - |
- O 在比赛中通过
- Ø 赛后通过
- ! 尝试了但是失败了
- - 没有尝试
Solutions
A.
题意:
给\(r\)个红豆和\(b\)个蓝豆,可以把它们装进不同袋子里,要求每个袋子都有红豆和蓝豆,且差值不超过\(d\),输出是否可行。
思路:
看最多能分几个袋子,乘上差值,加起来比较即可。
不开long long
见祖宗。
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m, d;
void solve() {
scanf("%lld %lld %lld", &n, &m, &d);
if (n < m) swap(n, m);
if (n > m + m * d) puts("NO");
else puts("YES");
}
int main() {
//ios::sync_with_stdio(false);
//cin.tie(nullptr);cout.tie(nullptr);
//cout << fixed << setprecision(20);
int t = 1;
//cin >> t;
t = rd();
while (t--) solve();
return 0;
}
B.
题意:
从矩阵左上角走到右下角,每次向下或向右走一步,向下走加上当前\(x\)的大小,向右走加上当前\(y\)的大小,问能否正好走到时,权值为\(k\)。
思路:
考虑向下走一步,让后面剩下的向右走的权值都加了\(1\),但是此步的权值也减少了相同多个x的数量,所以总权值不变。
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m, k;
void solve() {
n = rd();
m = rd();
k = rd();
if (n * (m - 1) + (n - 1) == k) puts("YES");
else puts("NO");
}
int main() {
//ios::sync_with_stdio(false);
//cin.tie(nullptr);cout.tie(nullptr);
//cout << fixed << setprecision(20);
int t = 1;
//cin >> t;
t = rd();
while (t--) solve();
return 0;
}
C.
题意:
有\(n\)个选手,每个选手的实力为\(s_i\)。学校编号为\(1\) ~ \(n\),每个选手的学校编号为\(u_i\)。假设想要\(k\)个人一组,每个学校会从大到小进行组织,小于\(k\)则不能组织成队伍。问\(k\)为\(1\) ~ \(n\)时,所有学校派出的选手实力和为多少。
思路:
没啥好的想法,发现暴力的复杂度是\(O(nlog_n^2)\),直接树状数组模拟了。
upd:
赛时写麻烦了。因为没有修改操作,直接求个前缀和减一减就行了。
#include <bits/stdc++.h>
using namespace std;
#define all(a) a.begin(), a.end()
#define endl "\n"
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 7;
ll bit[N];
int tot;
bool vis[N];
int id[N];
ll ans[N];
vector<int> G[N];
vector<int> cor;
inline int lowbit(int x) {
return x & -x;
}
void add(int i, ll v) {
while (i <= tot) {
bit[i] += v;
i += lowbit(i);
}
}
ll query(int i) {
ll res = 0;
while (i > 0) {
res += bit[i];
i -= lowbit(i);
}
return res;
}
void solve() {
int n = rd();
cor.clear();
for (int i = 1; i <= n; ++i) {
vis[i] = 0;
ans[i] = 0;
G[i].clear();
}
int tt = 0;
for (int i = 1; i <= n; ++i) {
id[i] = rd();
if (!vis[ id[i] ]) {
tt++;
vis[ id[i] ] = 1;
cor.push_back(id[i]);
}
}
ll t;
for (int i = 1; i <= n; ++i) {
scanf("%lld", &t);
G[ id[i] ].push_back(t);
}
for (int i = 1; i <= tt; ++i) {
int now = cor[i - 1];
tot = G[now].size();
sort(all(G[now]), greater<ll>());
for (int j = 1; j <= tot; ++j) bit[j] = 0;
for (int j = 1; j <= tot; ++j) {
ll v = G[now][j - 1];
add(j, v);
}
for (int gap = 1; gap <= tot; ++gap) {
int head = 0;
while (1) {
if (head + gap > tot) break;
ans[gap] += query(head + gap) - query(head);
head += gap;
}
}
}
for (int i = 1; i <= n; ++i) {
printf("%lld%c", ans[i], " \n"[i == n]);
}
}
int main() {
//ios::sync_with_stdio(false);
//cin.tie(nullptr);cout.tie(nullptr);
//cout << fixed << setprecision(20);
int t = 1;
//cin >> t;
t = rd();
while (t--) solve();
return 0;
}
D.
题意:
给两段等长序列\(a\),\(b\),对于\(a\),我们可以选择一个区间进行翻转,求\(max(\sum_{i = 1}^{n}a_i * b_i)\)。
思路:
题目数据明显想让我们\(O(n^2)\)去做。想到\(O(n^3)\)做法后,考虑如何减少一个\(n\)。发现如果每次找翻转的中心,分开奇偶讨论,就可以让翻转后区间的值具有延展性,就能做到\(O(n)\)去求了。那么再预处理下前后缀值即可。
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
inline int rd() {
int f = 0; int x = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) f |= (ch == '-');
for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + ch - '0';
if (f) x = -x;
return x;
}
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 5e3 + 8;
ll a[N], b[N];
ll pre[N], suf[N];
ll ans;
int n;
void work(int i, int j) {
if (j > n) return;
int l = i, r = j;
ll now = a[l] * b[r];
if (l != r) now += a[r] * b[l];
ans = max(now + pre[l - 1] + suf[r + 1], ans);
while (l > 1 && r < n) {
l--;
r++;
now += a[l] * b[r] + a[r] * b[l];
ans = max(now + pre[l - 1] + suf[r + 1], ans);
}
}
void solve() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
}
for (int i = 1; i <= n; ++i) {
cin >> b[i];
}
for (int i = 1; i <= n; ++i) {
pre[i] = pre[i - 1] + a[i] * b[i];
}
for (int i = n; i >= 1; --i) {
suf[i] = suf[i + 1] + a[i] * b[i];
}
for (int i = 1; i <= n; ++i) {
work(i, i);
work(i, i + 1);
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cout << fixed << setprecision(20);
int t = 1;
//cin >> t;
// t = rd();
while (t--) solve();
return 0;
}
E.
题意:
给定二维平面,每个点都在第一象限。你每次可以选两个点,每个点都需要向上走一步或者向下走一步,并让这两个点和\((0, 0)\)共线,然后就可以将这两个点从平面上拿走。问你最多可以拿几个点,并输出方案。
思路:
这题也太难了。
一看题意,感觉很像二分图匹配,但是图都建不出来。
我们考虑三点共线,即两个点与\((0, 0)\)点的斜率相同。对于一个点来说,可以走到两个地方,且这两个地方的斜率是不同的,那么就很像一条边有两个点。从这出发,我们将斜率作为新图的点,将点作为新图的边,就能建一张新图了。在这个图上,这道题目的问题就变成了求几对边,使得每对边都有相交的点。
点和边很多,肯定不能跑二分图匹配。我们考虑能否贪心地去匹配。我们发现对于一个斜率来说,如果能到它的点是偶数个的话,就能直接分配完,否则会剩下一个。那剩下那个就一定匹配不了了吗?对于无向图来说,我们直接这样贪肯定是不对的,边的关系太复杂了。考虑通过\(dfs\)将其转换成有向图。
在有向图上,我们可以优先满足子图的匹配。如果子图中还有剩下没有匹配完的,再将其传回父图,用其他子图剩下来的边或者来时的边(注意这里的边指的是新图的边,即原图的点)和它匹配,就能做到最优匹配了。
#include <bits/stdc++.h>
using namespace std;
#define endl "\n"
typedef long long ll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int N = 2e5 + 7;
bool vis[N];
ll a, b, c, d;
int head[N];
int tot, n, m;
map<pair<ll, ll>, int> mp;
vector< pair<int, int> > ans;
vector< tuple<int, int> > G[N << 1];
int getHash(ll x, ll y) {
if (!mp.count({x, y})) {
mp[{x, y}] = ++m;
return m;
} else {
return mp[{x, y}];
}
}
int dfs(int u, int now) {
vis[now] = 1;
int p = 0; // p记录当前子图没有匹配上的边(原图中的点)
for (auto t : G[u]) {
int v, id;
tie(v, id) = t;
if (vis[id]) continue;
int s = dfs(v, id);
if (s) {
if (!p) {
p = s;
} else {
ans.push_back({p, s}); // 有两个即可匹配
p = 0; // 注意清空
}
}
}
if (p) {
if (now) {
ans.push_back({p, now}); // 来时的边也能进行匹配
p = 0;
}
} else {
p = now;
}
return p;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> a >> b >> c >> d;
ll x = a * d, y = c * b, w = b * d; // 数据很大,直接浮点数除精度必炸。
ll g1 =__gcd(x + w, y), g2 = __gcd(x, y + w); // 考虑两个分母相同的分数相除,相当于分子相除
int u = getHash((x + w) / g1, y / g1); // 那么我们就只用记录两个分子即可
int v = getHash(x / g2, (y + w) / g2); // 注意化成最简,(1, 2)与(4, 8)的斜率是相同的
G[u].emplace_back(v, i);
G[v].emplace_back(u, i);
head[i] = u; // 记录点的其中一个能到的斜率,作为dfs的入口
}
for (int i = 1; i <= n; ++i) { // 遍历原图的点
if (!vis[i]) dfs(head[i], 0);
}
cout << ans.size() << endl;
for (auto t : ans) {
cout << t.first << " " << t.second << endl;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);cout.tie(nullptr);
cout << fixed << setprecision(20);
int t = 1;
while (t--) solve();
return 0;
}