Codeforces Round 888 (Div. 3)

D: 参考题解(https://zhuanlan.zhihu.com/p/646013267)

题意:给你一个n的全排列的全缀和数组,但是会漏掉一个 => 就只给你 n - 1 个前缀和数组问你能不能找到n的一个全排列使得它的前缀和数组满足题目要求。

思路:因为给你的是前缀和数组,所以相邻两项作差得到的数字有两种可能:

            1. x 是全排列中的一个数字

            2. x 是全排列中两个数字的和

因为只删掉了前缀和数组中的一个,所以情况二最多只会出现一次,那要如何判断情况二是否出现了呢?如果当前的 x 大于 n 或者已经在前面出现过了那就说明情况二出现了。

void solve()
{
    int n; cin >> n;
    std::vector s(n + 1, 0);
    for (  int i = 1; i <= n - 1; i++)
        cin >> s[i];

    std::vector a;

    for (  int i = 1; i <= n - 1; i++)
    {
        LL x = s[i] - s[i - 1];
        if (x >= 1 && x <= n && !st[x]) st[x] =   true ;
        else a.push_back(x);   // 情况二
    }

    if (a.size() >= 2) cout <<   "No" << endl;
    else if (a.size() == 0) cout <<   "Yes" << endl;
    else // 看看剩下的两个数能不能拼成
    {
        LL sum = 0;
        for (  int i = 1; i <= n; i++)
            if (!st[i]) sum += i;
        if (sum == a[0]) cout <<   "Yes" << endl;
        else cout <<   "No" << endl;
    }
    memset (st, 0,   sizeof (st));
}

  

E:参考代码(applese大佬)(https://codeforces.com/contest/1851/standings)

题意:现有 n 种药水,你有其中的 k 种,每一种药水的价格为c[i], 现在给你每种药水的合成路径,问如果你想获得 1-n 的所有药水那么每种药水的最少花费是多少? 保证药水不能由自身合成获得

思路:可以将药水之间的合成关系建图,然后按照拓扑排序的方式顺次处理每一种药水,为什么按拓扑序枚举,因为一个点如果已经求出了答案,那么它的入度就为零

void solve()
{
    int n, k; cin >> n >> k;
    std::vector<int> in(n + 1, 0); // 入度
    std::vector<int> c(n + 1); // 每一种药的花费
    std::vector<LL> ans(n + 1, 1e18); // 答案
    std::vector<int> vis(n + 1); // 哪些药已经有了
    std::vector<std::vector<int>> g(n + 1); // 存图
    std::vector<LL> sum(n + 1, 0); // 合成该药的花费

    for (int i = 1; i <= n; i++)
        cin >> c[i];
    
    for (int i = 0; i < k; i++)
    {
        int x; cin >> x;
        vis[x] = 1;
    }

    for (int i = 1; i <= n; i++)
    {
        int m; cin >> m;
        for (int j = 0; j < m; j++)
        {
            int x; cin >> x;
            g[x].push_back(i);
            in[i]++;
        }
    }


    queue<int> q;
    for (int i = 1; i <= n; i++)
        if (in[i] == 0)
            q.push(i), sum[i] = 1e18;
        
    while (!q.empty())
    {
        int t = q.front();
        q.pop();

        ans[t] = min(sum[t], (LL)(vis[t] ? 0 : c[t]));

        for (auto x : g[t])
        {
            sum[x] += ans[t];
            in[x]--;
            if (in[x] == 0)
            {
                q.push(x);
            }
        }
    }

    for (int i = 1; i <= n; i++)
        cout << ans[i] << ' ';
    cout << endl;
}

F: 参考链接(https://www.cnblogs.com/bible-/p/17581785.html)

题意:

先给你n个正整数的序列a,然后再给一个k, 让你在a中找两个整数ai, aj, 在 [0, 2^k) 中找一个数x,使得(ai ^ x) & (aj ^ x)尽可能的小,求最小异或对可以用trie树,

思路:

对于ai和aj都要异或x,对于二进制第k位,只有当ai和aj的第k位相等,且与x的第k位不同,才会对ans的第k位贡献1;

要让ai和aj的每一位尽可能都相等,就要让ai异或aj最小,找出最小的异或对:n个数的序列中的最小异或对为序列排序后最小的相邻异或和

找到ai和aj后,枚举x的每一位,若ai和aj当前位一样都为0,那么x的当前位为1;若ai和aj当前位一样都为1,那么x的当前位为0

 

void insert(int x)
{
    int p = 0;
    for (int i = k - 1; i >= 0; i--)
    {
        int u = x >> i & 1;
        if (!trie[p][u]) trie[p][u] = ++idx;
        p = trie[p][u];
    }
}

int query(int x)
{
    int p = 0, ans = 0;
    for (int i = k - 1; i >= 0; i--)
    {
        int u = x >> i & 1;

        if (trie[p][u] != 0)
        {
            p = trie[p][u];
            ans = ans * 2 + u;
        }
        else
        {
            p = trie[p][!u];
            ans = ans * 2 + !u;
        }
    }

    return ans;
}


void solve()
{
    cin >> n >> k;

    int t1 = 0, t2 = 0, a1 = 0, a2 = 0;
    LL minn = 1e18;
    map<int, std::vector<int>> c;
    for (int i = 1; i <= n; i++)
    {   
        int x; cin >> x;
        c[x].push_back(i);
        if (i != 1)
        {
            int t = query(x);
            if ((x ^ t) < minn)
            {
                a1 = x;
                a2 = t;
                t1 = i;
                for (auto x : c[t])
                {
                    if (x != i)
                    {
                        t2 = x;
                        break;
                    }
                }
                minn = x ^ t;
            }
        }
        insert(x);
    }

    int ans = 0;
    for (int i = k - 1; i >= 0; i--)
    {
        if ((a1 >> i & 1) == (a2 >> i & 1) && (a1 >> i & 1) == 0) ans = ans * 2 + 1;
        else ans = ans * 2 + 0;
    }

    cout << t1 << ' ' << t2 << ' ' << ans << endl;

    idx = 0;
    for (int i = 0; i <= n * k; i++)
    {
        trie[i][1] = trie[i][0] = 0;
    }
}

G: 参考代码jiangly(https://codeforces.com/contest/1851/standings)

题意: 给你 n 座山的高度h1 - hn, 以及 m 条路,每条路连着两座山,从 hi 到 hj 的能量消耗定义为 hj - hi, 如果cost为负数则不能翻阅这座山。现在给你 q 个询问,每个询问包含三个整数u ,v, e。u为起点,v为终点,e为初始能量(可补充),问是否存在一条路径使得你可以从 u 走到 v?

思路:对于每条路来说,这条路能不能走取决于我的能量是不是大于 max(hi, hj),因为我得先爬上 hi 才能通过这条路爬到 hj.  所以边权可以定义为 max(hi, hj). 所以 u 能不能到 v 关键取决于两点:

1. u 到 v 必须是连联通

2. hu + e >= 路径上最高的一座山,因为走低山,h变小但e变大总量不变

由 1 容易想到并查集,因为查询是独立的,每一次查询时间复杂度为O(qmlog(n)),所以要离线先把所用查询先处理好, 先把所有边按边权从小到大排序(避免多次查询),然后把所有查询按照初始能量从小到大排序(避免前面有个很大的能量查询,把所有点都联通了造成误判),那样遍历所有查询的时候就把所有符合2的点放入一个连通块

#include <bits/stdc++.h>

using namespace std;
#define more ios_base::sync_with_stdio(0),cin.tie(0),cout.tie(0);
const int N = 2e5 + 5, MOD = 1e9 + 7;
const long long INF = 1e18;
typedef long long LL;
typedef unsigned long long usLL;
typedef pair<int, int> PII;

struct DSU
{
    std::vector<int> f, siz;
    DSU(){}
    DSU(int n)
    {
        init(n);
    }

    void init(int n)
    {
        f.resize(n);
        iota(f.begin(), f.end(), 0); // 从0开始自增赋值
        siz.assign(n, 1);   
    }

    int find(int x)
    {
        while (x != f[x])
        {
            x = f[x] = f[f[x]];
        }

        return x;
    }

    bool same(int x, int y)
    {
        return find(x) == find(y);
    }

    bool merge(int x, int y)
    {
        x = find(x); y = find(y);
        if (x == y)
            return false;
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }

    int size(int x)
    {
        return siz[find(x)];
    }
};

void solve() 
{
    int n, m; cin >> n >> m;
    vector<int> h(n + 1);
    for (int i = 1; i <= n; i++) cin >> h[i];

    vector<tuple<int, int, int>> edges(m + 1);
    for (int i = 1; i <= m; i++)
    {
        int u, v; cin >> u >> v;
        edges[i] = {max(h[u], h[v]), u, v}; // 存下边的大小,起点, 终点
    }

    sort(edges.begin(), edges.end());

    int q; cin >> q;

    vector<tuple<int, int, int, int>> qry(q + 1);

    for (int i = 1; i <= q; i++)
    {
        int a, b, e; cin >> a >> b >> e;
        qry[i] = {h[a] + e, a, b, i};
    }

    sort(qry.begin(), qry.end()); // 保证枚举每个查询的时候,当前的连通块都是你可以到的点

    DSU dsu(n + 1);

    int j = 1;
    vector<bool> ans(q + 1);

    for (auto [w, u, v, i] : qry)
    {
        while (j <= m && get<0>(edges[j]) <= w) // 对于当前查询的能量值来说,把所有能到的点放入一个连通块
        {
            dsu.merge(get<1>(edges[j]), get<2>(edges[j]));
            j++;
        }
        ans[i] = dsu.same(u, v); // 最后查一下,起点和终点是否联通
    }
    for (int i = 1; i <= q; i++)
    {
        if (ans[i]) cout << "Yes" << endl;
        else cout << "No" << endl;
    }

    cout << "\n";
}

int main()
{
    more;
    int T;
    cin >> T;
    while (T--)
    {
        solve();
    }
    //solve();
    return 0;
}

 

posted @ 2023-07-27 18:43  自动机  阅读(78)  评论(0)    收藏  举报