Educational Codeforces Round 108 (Rated for Div. 2)

Contest Info


Practice Link

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;
}
posted @ 2021-04-30 14:26  stff577  阅读(81)  评论(0)    收藏  举报