CF #760 Div3(A - G)

A.Polycarp and Sums of Subsequences

题意:

  有一个数组 \(a\) 长度为 \(3\) ,将所有的非空集合中的数全都加起来可以得到 \(b\) .已知数组 \(b\) ,返回去求 \(a\)

思路:

  假定 \(a = \{x, y, z\}\) , 且 \(x \leq y \leq z\) , 那么\(b\) 就是 \(\{x, y, z, x + y, y + z, x + z, x + y + z\}\) ,因为确定了 \(x \leq y\leq z\) ,所以 \(x \leq y \leq \min\{x + y, z\}\), 所以只需要判断第三个数是 \(x + y\) 还是 \(z\) 就可以了

    int b[7];

    void solve() {
        std::set<int> s;
        for (int i = 0; i < 7; i++) std::cin >> b[i];
        int a[3] = {b[0], b[1]};
        if (b[0] + b[1] == b[2] && b[2] <= b[3]) a[2] = b[3];
        else a[2] = b[2];
        for (int i = 0; i < 3; i++) std::cout << a[i] << " \n"[i == 2];
    }

B.Missing Bigram

题意:

  定义一种字符串 \(bigram\) 是一个长度为 \(2\) 的子串(子串的定义是$s[i\dots j] = s[i] + s[i + 1] \dots + s[j - 1] + s[j]) $ .按照出现的先后顺序给出 \(n - 2\)\(bigram\) 要构造出原串 \(s\) 是什么

思路:

  先考虑原串怎么写出所有的 \(bigram\) ,因为每个子串都是 \(s[i, i + 1]\) 所以一共可以写出 \(n - 1\) 个子串,那现在给出 \(n - 2\)\(bigram\) 就说明中间跳过了一个字符没写出以它为首字母的 \(bigram\) 。所以去判断 \(s[i - 1][1] == s[i][0]\) 这个条件成不成立,成立的话就说明这个是连续的中间没有跳过一个字符,当这个条件不成立的时候,就是此处省略了一个 \(bigram\) 。构造方法就是如果是连续的 \(bigram\) 就将 \(s[i][1]\) 加入到构造的字符串中来,不然的话就将 \(s[i][0], s[i][1]\) 都加入到构造的字符串中来。

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

        std::string ans;
        ans = s[0];
        for (int i = 1; i < n - 2; i++) {
            if (s[i][0] == s[i - 1][1]) ans += s[i][1];
            else ans += s[i];
        }
        if (ans.size() != n) ans += s.back()[1];
        std::cout << ans << "\n";
    }

C.Paint the Array

题意:

  给一个正整数序列 \(a\) ,选择一个正整数 \(d\) 让每个 \(d \mid a_i\) 染上红色, $ d \nmid a_i$ 染上蓝色。最终要满足 \(a\) 中相邻的数不能染上相同的颜色, 求出 \(d\) 是多少

思路:

   首先注意到相邻的数不能染上相同的颜色那么就不难想到对数组的下标分奇偶去分类讨论。我们要让所有下标为奇数的都能够被 \(d_1\) 整除, 所有下标为偶数的都能够被 \(d_2\) 整除,那么就是求 \(a_{\{i \mathrel{|} i \in 2k - 1, k = 1, 2, \dots \frac{n}{2}\}}\) 的最大公约数 \(\gcd\) , 同理求 \(a_{\{i \mathrel{|} i \in 2k, k = 1, 2, \dots \frac{n}{2}\}}\) 的最大公约数 \(\gcd\) 。这样就将 \(d\) 锁定在了 \(d1, d2\) 两个数之间。现在只需要判断 \(d1, d2\) 能不能满足不让相邻的数涂上相同的元素这个条件了,也就可以转化成 \(d1\) 能不能整除下标为偶数的数和 \(d2\) 能不能整除下标为奇数的数,如果可以整除的话,就说明会出现相邻的数染上相同的元素的情况,所以这个数的不符合要求了,如果最后 \(d1, d2\) 都是可行的,就输出最大的那一个
   \(STL\) 福利

    void solve() {
        int n; std::cin >> n;
        std::vector<long long> a(n);
        for (auto& I : a) std::cin >> I;

        std::array<long long, 2> d = {};
        std::array<bool, 2> f = {true, true};
        for (int i = 0; i < n; i++) 
            d[i & 1] = std::__gcd(d[i & 1], a[i]);
        for (int i = 0; i < n; i++) 
            f[i & 1] = f[i & 1] && (a[i] % d[(i & 1) ^ 1]);

        long long ans = 0;
        for (int x : {0, 1}) 
            if (f[x]) ans = std::max(ans, d[x ^ 1]);

        std::cout << ans << "\n";
    }

   正常的代码

    void solve() {
        int n; std::cin >> n;
        std::vector<long long> a(n);
        for (int i = 0; i < n; i++) std::cin >> a[i];

        if (n == 1) return void(std::cout << a[0] << "\n");
        long long d1 = 0, d2 = 0;
        for (int i = 0; i < n; i++) {
            if (i & 1) d1 = std::__gcd(d1, a[i]);
            else d2 = std::__gcd(d2, a[i]);
        }
        
        int f1 = 0, f2 = 0; // 判断是不是可行的
        for (int i = 0; i < n; i++) {
            if (i & 1) f1 |= !(a[i] % d2);
            else f2 |= !(a[i] % d1);
        }
    
        if (!f1 && !f2) std::cout << std::max(d1, d2) << "\n";
        else if (!f1) std::cout << d2 << "\n";
        else if (!f2) std::cout << d1 << "\n";
        else std::cout << 0 << "\n";
    }

D. Array and Operations

题意

给定 \(n\) 个数和一个数 \(k\),要求对这 \(n\) 个数进行 \(k\) 次操作,每次操作取出两个数 \(a_i,a_j (i\neq j)\),并将 \(\lfloor \frac{a_i}{a_j} \rfloor\) 加进得分中,其中 \(\lfloor \frac{x}{y} \rfloor\) 为不超过 \(\frac{x}{y}\) 的最大整数。\(k\) 次操作后,将剩下的 \(n-2k\) 个数直接加入到得分中。求最终得分的最小值。

思路:

因为要让剩下的数之和最小,就要尽可能的消耗掉大的数,所以先对数组从小到大排序。然后让最大的 \(2k\) 个数互相消耗。要保证尽可能的使得 \(\frac{x}{y}\) 小,就是要尽可能的让 \(y > x\) 所以让 \(a_{i - k}\) 去消耗 \(a_{i}\) 这样能够尽可能的使得 \(\frac{x}{y}\)\(0\)

    void solve() {
        int n, k;
        std::cin >> n >> k;
        std::vector<int> a(n);
        for (int i = 0; i < n; i++) std::cin >> a[i];
    
        std::sort(a.begin(), a.end());
        long long ans = 0;
        for (int i = n - 2 * k; i < n - k; i++) {
            ans += a[i] / a[i + k];
        }
        ans += std::accumulate(a.begin(), a.begin() + n - 2 * k, 0ll);
        std::cout << ans << "\n";
    }

E.Singers' Tour

题意

\(n\) 个城镇依次排列成一个圆圈,按顺时针从 \(1\)\(n\) 编号。在第 \(i\) 个镇中有一个歌手,Ta起初只会演奏一首长度为整数 \(a_i\) 的曲子。每位歌手从他居住的城镇开始,按顺时针顺序依次访问所有 \(n\) 个城镇,并在每个城镇举办恰好一场音乐会。此外,在每个城镇,第 \(i\) 个歌手都得到灵感,创作了一首长度 \(a_i\) 分钟的歌曲。这首歌被添加到他的曲目中,并在后来的所有城市被演奏。因此,对于第 \(i\) 个歌手,在第 \(i\) 个城镇的音乐会将持续 \(a_i\) 分钟,在第 \((i + 1)\) 个城镇的音乐会将持续 \(2 \cdot a_i\) 分钟, ..., 在第 \(((i + k) \bmod n + 1)\) 个城镇的音乐会将持续 \((k + 2) \cdot a_i\) , ..., 在第 \(((i + n - 2) \bmod n + 1)\)个城镇的音乐会将持续 \(n \cdot a_i\) 分钟。给定一个 \(b\) 整数数组,其中 \(b_i\) 是第 \(i\) 个城镇的所有音乐会的总时长。如果可以根据 \(b\) 恢复序列 \(a\) ,则输出YES和任意正确的 \(a\)。否则输出NO

思路:

枚举可以发现 \(b_1 = a_1 + na_2 + (n - 1)a_3 + (n - 2)a_4 + \dots + 2a_n\) , \(b_2 = 2a_1 + a_2 + na_3 + (n - 1)a_4 + \dots + 3a_n\) \(\dots\) 所以将所有的 \(b_i\) 加起来,\(\sum b_i = \frac{n(n + 1)\sum a_i}{2}\) 那么可以求出来 \(\sum a_i = \frac{2 \sum b_i}{n(n + 1)}\) 然后让 \(b_i\) 相邻两项相减会得到 \(b_i - b_{i - 1} = a_1 + a_2 + \cdots + (1 - n)a_i + \cdots + a_n\) 所以每一项的 \(a_i = \frac{b_{i - 1} - b_i + \sum a_i}{n}\)

    void solve() {
        int n;
        std::cin >> n;
        std::vector<int> b(n + 1);
        for (int i = 1; i <= n; i++) std::cin >> b[i];
        long long cur = std::accumulate(b.begin() + 1, b.end(), 0ll);
        
        int d = n * (n + 1) / 2;
        if (cur % d != 0) return void(std::cout << "NO\n");
        cur /= 1ll * (n + 1) * n / 2;

        std::vector<int> a(n + 1);
        b[0] = b[n];
        for (int i = 1; i <= n; i++) {
            long long res = b[i] - b[i - 1];
            res -= cur;
            if (res % n || res >= 0) return std::cout << "NO\n", void();
            a[i] = -res / n;
        }
        std::cout << "YES\n";
        for (int i = 1; i <= n; i++) std::cout << a[i] << " \n"[i == n];
    }

F. Reverse

Reverse

题面翻译

对于一个二进制数 \(x\),每次操作可以在它的末尾加一个 \(0\) 或者 \(1\),然后将其翻转(自动去除前导 \(0\))。如:\((34)_{10}=(100010)_2\),可以在它的后面加上 \(1\) 然后翻转得到 \((1010001)_2=(81)_{10}\)。也可以在它的后面加上 \(0\) 翻转得到 \((10001)_2=(17)_{10}\)\((81)_{10}=(1010001)_2\),可以在它的后面加上 \(0\) 翻转得到 \((1000101)_2=(69)_{10}\)。所以可以先将 \(34\) 变为 \(81\),再将 \(81\) 变为 \(69\)。现在给定两个正整数 \(x\)\(y\ (x,y\le 10^{18})\),问能否通过若干次操作后将 \(x\) 变成 \(y\)。输出 YESNO

思路:

先考虑在末尾加 \(0\)\(1\) 再翻转对结果有什么影响。在末尾加 \(0\) 再翻转那么前导零都对 \(x\) 没有任何影响了,所以末尾加 \(0\) 就相当于将二进制下所有的后导零都删掉之后,再将二进制翻转。末尾加 \(1\) 就是将二进制翻转后的最前面加 \(1\) 。试图去找规律没有找到,所以应该是不存在什么神秘的结论,就考虑将所有的情况都搜出来,可以用 \(BFS\)\(DFS\) 来将所有的情况都找出来,最后判断 \(y\) 有没有出现过就可以了。

    std::string op(std::string s) {
        while(s.back() % 2 == 0) s.pop_back();
        std::reverse(s.begin(), s.end());
        return s;
    }

    std::string turn(long long x) {
        std::string s;
        while(x) {
            s += ('0' + x % 2);
            x >>= 1;
        }	
        return s;
    }

    void solve() {
        long long x, y;
        std::cin >> x >> y;
        std::map<std::string, int> s;
        s[turn(x)];
        std::queue<std::string> q;

        q.push(turn(x));
        while(q.size()) {
            std::string p = q.front();
            q.pop();

            if (p.size() > 70) continue;
            for (int o : {0, 1}) {
                std::string cur = op(p + std::string(1, '0' + o));
                if (!s.count(cur)) {
                    q.push(cur);
                    s[cur];
                }
            }
        }

        std::cout << (s.count(turn(y)) ? "YES\n" : "NO\n");
    }

G. Trader Problem

题意

在交易系统中,你可以用一个价值为 \(x\) 的物品换取一个价值不超过 \(x+k\) 的物品(\(k\) 为常数)。给定你手中的 \(n\) 个物品,以及系统的 \(m\) 个物品分别的价值,有 \(q\) 次询问,对于每次询问,给定 \(k\),求经过若干次交易后你手上物品的总价值最大是多少。

思路:

因为可以用 \(x\) 价值的物品去换到所有价值不超过 \(x + k\) 的某一个物品,所以也就是要去维护差值为 \(k\) 的所有的点的集合。而每次的差值都是询问的时候才知道的,所以考虑离线的做法,将所有的询问都存下来,并且按照 \(k\) 从小到大存下相应询问的答案。维护所有差值为 \(k\) 的集合考虑用并查集去维护。具体来说,记录排序后相邻物品价值差为定值时物品的下标,则当存在 \(val_i-val_{i-1}\leq k\) 时,需要将 \(i\)\(i−1\) 合并,同时重新计算它们对答案的贡献。

    int n, m, q;
    std::cin >> n >> m >> q;
    std::vector<std::array<int, 2>> a(n + m + 1);
    for (int i = 1; i <= n + m; i++) {
    	std::cin >> a[i][0], a[i][1] = i;
    }

    std::vector<i64> sum(n + m + 1, 0);
    std::vector<int> p(n + m + 1);
    std::iota(p.begin(), p.end(), 0);

    std::function<int(int)> find = [&](int x) -> int {
    	return x == p[x] ? p[x] : p[x] = find(p[x]);
    };

    std::sort(a.begin() + 1, a.end());
    std::vector<std::array<int, 2>> event(q + 1);
    std::vector<int> siz(n + m + 1);
    for (int i = 1; i <= q; i++) std::cin >> event[i][0], event[i][1] = i;

    i64 res = 0;
    std::map<int, std::vector<int>> mp;
    for (int i = 1; i <= n + m; i++) {
    	siz[i] = (a[i][1] <= n);	
    	sum[i] = sum[i - 1] + a[i][0];
    	if (a[i][1] <= n) res += a[i][0];
    	if (i > 1) mp[a[i][0] - a[i - 1][0]].push_back(i - 1);
    }

    std::sort(event.begin() + 1, event.end());
    std::vector<i64> ans(q + 1);
    auto it = mp.begin();
    for (int i = 1; i <= q; i++) {
    	if (event[i][0] == event[i - 1][0]) {
    		ans[event[i][1]] = res;
    		continue;
    	}

    	while(it != mp.end() && it -> first <= event[i][0]) {
    		for (auto& k : it -> second) {
    			int x = find(k), y = find(k + 1);
    			res -= (sum[y] - sum[y - siz[y]]) + (sum[x] - sum[x - siz[x]]);
    			siz[y] += siz[x];
    			p[x] = y;
    			res += (sum[y] - sum[y - siz[y]]);
    		}
    		++it;
    	}
    	ans[event[i][1]] = res;
    }

    for (int i = 1; i <= q; i++) std::cout << ans[i] << "\n";

posted @ 2022-10-07 01:31  浅渊  阅读(77)  评论(0)    收藏  举报