【CF】Day16——Codeforces Round 956 (Div. 2) and ByteRace 2024 C + 拓扑排序 + 树上贪心二分

C. Have Your Cake and Eat It Too

题目:

思路:

暴力,太暴力了

简化一下题意,就是让我们在a,b,c三个数组中选3段无重复的连续区间,使得每段区间和都是sum/3,其中sum是每个数组的和,并且保证每个数组和相同

既然要选区间,那么最后肯定是如下图所示的选法

即一段左边,一段中间,一段右边

但是谁中间谁左边谁右边呢?

无所谓,暴力枚举会拯救一切,我们直接排列组合一下即可,发现最多就6种情况,所以我们可以枚举

那么如何选区间呢?我们可以用两个指针,左指针从左边开始累加,如果总和大于sum/3那就停止,右指针同理,最后 l~r 就是中间的和了,然后判断一下是否可行即可,如果可以就输出,否则看下一种情况 ,如果都不行就输出-1

代码:

#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"
int n;
void Print(const vector<pair<int, int>>& ans)
{
    cout << ans[0].first << " " << ans[0].second << " ";
    cout << ans[1].first << " " << ans[1].second << " ";
    cout << ans[2].first << " " << ans[2].second << endl;
}
bool check(const int& sum,const vector<int>& a, const vector<int>& b, const vector<int>& c,
    const int& o, const int& t, const int& s)
{
    vector<pair<int, int>> ans(3, { 0,0 });
    int l = 1, r = n;
    int sa = 0, sb = 0, sc = 0;
    while (sa < sum)
    {
        sa += a[l++];
    }
    while (sc < sum)
    {
        sc += c[r--];
    }
    for (int i = l; i <= r; i++)
    {
        sb += b[i];
    }
    if (sa >= sum && sb >= sum && sc >= sum)
    {
        ans[o-1] = { 1,l-1};
        ans[t-1] = { l,r };
        ans[s-1] = { r+1,n};
        Print(ans);
        return true;
    }
    return false;
}

void solve()
{
    cin >> n;
    vector<int> a(n+1), b(n+1), c(n+1);
    int sum = 0;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum += a[i];
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> b[i];
    }
    for (int i = 1; i <= n; i++)
    {
        cin >> c[i];
    }
    sum = (sum + 2) / 3;
    if (check(sum, a, b, c, 1, 2, 3))return;
    if (check(sum, a, c, b, 1, 3, 2))return;
    if (check(sum, b, a, c, 2, 1, 3))return;
    if (check(sum, b, c, a, 2, 3, 1))return;
    if (check(sum, c, b, a, 3, 2, 1))return;
    if (check(sum, c, a, b, 3, 1, 2))return;
    cout << -1 << endl;
}

signed main()
{
    cin.tie(0)->sync_with_stdio(false);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

拓扑排序

题目:

思路:

题目让我们将无向边变为有向边,使得最后的图无环,那么一个显然的做法就是先看一开始是否有环,如果无的话那么一定可以,否则不行

那么我们分步来写,我们首先要判断是否是一个 有向无环图,这里我们可以使用拓扑排序

拓扑排序的步骤:找到入度为0的点,将其假如队列,然后从队列中取出点,并将其子节点的入读-1,如果其子节点减完后入读也是0,那么就将其加入队列,如此往复,如果最后能把所有点都找过一遍,说明无环,否则有环

那么判断完后我们就得到拓扑排序了,这时我们就可以将无向边按照拓扑排序的优先级直接判断了,具体操作是,对于 u v 两点,如果拓扑排序中 u 在 v 前面,那就使 u -> v,否则就是 v -> u,这样我们按照拓扑排序的顺序操作到最后一定不会有环

代码:

#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"

void solve()
{
    int n, m1, m2;
    cin >> n >> m1 >> m2;
    vector<pair<int,int>> gnone;
    vector<vector<int>> ghas(n+1);
    vector<int> d(n + 1,0);
    vector<int> list(n + 1, 0);
    for (int i = 0; i < m1; i++)
    {
        int u, v;
        cin >> u >> v;
        gnone.push_back({ u,v });
    }
    for (int i = 0; i < m2; i++)
    {
        int u, v;
        cin >> u >> v;
        ghas[u].push_back(v);
        d[v]++;
    }
    int index = -1;
    int cnt = 0;
    queue<int> q;
    for (int i = 1; i <= n; i++)
    {
        if (!d[i])
            q.push(i);
    }
    while (!q.empty())
    {
        int t = q.front();
        list[t] = n - cnt;
        q.pop();
        cnt++;
        for (auto son : ghas[t])
        {
            d[son]--;
            if (!d[son])
                q.push(son);
        }
    }
    if (cnt < n)
    {
        cout << "-1\n";
        return;
    }
    for (int i = 0;i < m1;i++)
    {
        if (list[gnone[i].first] < list[gnone[i].second])
            swap(gnone[i].first, gnone[i].second);
        cout << gnone[i].first << " " << gnone[i].second << endl;
    }
}

signed main()
{
    cin.tie(0)->sync_with_stdio(false);
    int t = 1;
    while (t--)
    {
        solve();
    }
    return 0;
}

树上贪心二分

题目:

思路:

一个很显然的二分,最大值的最小值

那我们如何check呢?我们可以考虑从叶子节点往上累加,当一个点有两颗或以上的子树时,我们就要考虑贪心了,因为我们要考虑最小值,那么对于这个节点,我们就要选其所有子树中最小的那条线段,为什么?

如果我们不选最小的,假设这个节点的父亲就是根,那么线段长就是 len + 1,而选了最短的 mi + 1,其中 len > mi,显然肯定是选短的好,因此贪心成立

注意细节方面,在dfs中我们返回的是最小值,但是我们还要时刻记录最大值,比如一个节点的子树的线段分别是 4 2,而我们的 mid 是 3,那么遇到 4 的时候就要提前结束了

具体实现看代码 

代码:

#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"
const int INF = 1e9;
void solve()
{
    int n;
    cin >> n;
    vector<vector<int>> g(n + 1);
    for (int i = 2; i <= n; i++)
    {
        int father;
        cin >> father;
        g[father].push_back(i);
    }
    auto dfs = [&](auto dfs,int fa,int mid)->int{
        if (!g[fa].size()) return 0;
        int mx = -INF;
        int mi = INF;
        for (auto x : g[fa])
        {
            int t = dfs(dfs, x, mid);
            if (t == INF)
                return INF;
            mx = max(mx, t+1);
            if (mx > mid)
                return INF;
            mi = min(mi, t + 1);
        }
        return mi;
        };
    auto check = [&](int mid) {
        return dfs(dfs,1,mid) != INF;
        };
    int l = 0, r = n - 1;
    while (l + 1 < r)
    {
        int mid = l + r >> 1;
        if (check(mid))
        {
            r = mid;
        }
        else
        {
            l = mid;
        }
    }
    cout << l+1 << endl;
}

signed main()
{
    cin.tie(0)->sync_with_stdio(false);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值