Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020 - Final)

Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020 - Final)

A. Kids Seating

题意: 给定n,从1~n*4中选出 n个数,使得任意两个数不互质且没有整除关系。

题解: 看到互质关系,想到间隔2取数,看到整除关系,就想到尽可能让数字靠向4n。那么直接取4n - 2, 4n - 4, …, 2n

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;

int main() {
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        cin >> n;
        int cur = 4 * n - 2;
        for (int i = 1; i <= n; ++i) {
            cout << cur << " ";
            cur -= 2;
        }
        cout << endl;
    }
    return 0;
}

B. Saving the City

题意: 给一段序列,1为有炸弹,0为无炸弹,要引爆所有炸弹,同时一个炸弹可以触发前后两个的炸弹。引爆一个炸弹的钱是a,放一个炸弹的钱是b。问引爆所有炸弹的最少花费。字符串长度为 1 0 5 10^5 105 1 < = a , b < = 1000 1<=a,b<=1000 1<=a,b<=1000

题解: 如果想要把所有的炸弹引爆,那么要不然不按照炸弹。如果要安装炸掉,必然是先装0少的地方。每次填满一段0,就会使得1的连续数目段减一。所有只需要把0的段长度截出来,然后按照从小到大排序,不断往里面填炸掉。因此答案的为: m i n { a ∗ c n t 1 , a ∗ ( c n t 1 − 1 ) + c n t 0 [ 1 ] ∗ b , a ∗ ( c n t 1 − 2 ) + ( c n t 0 [ 1 ] + c n t 0 [ 2 ] ) ∗ b , . . . } min\{a* cnt_1, a*(cnt_1 - 1) + cnt_0[1] * b, a*(cnt_1-2)+(cnt_0[1]+cnt_0[2])*b,...\} min{acnt1,a(cnt11)+cnt0[1]b,a(cnt12)+(cnt0[1]+cnt0[2])b,...}。需要不断抠出来1的段和0的段。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;
int v[N], sz[N];

int main() {
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        memset(v, 0, sizeof v);
        memset(sz, 0, sizeof sz);
        cin >> n >> m;
        string s;
        cin >> s;
        int cnt = 0;
        for (int i = 0, j = 0; i < s.size(); i = j) {
            j = i;
            while(j < s.size() && s[i] == s[j]) j++;
            v[cnt] = s[i] - '0';
            sz[cnt] = j - i;
            cnt++;
        }
        // cout << 11 << endl;
        vector<int> vec;
        int cnt_one = 0;
        for (int i = 0; i < cnt; ++i) 
            if (v[i] == 0) {
                if (i && i < cnt - 1) vec.push_back(sz[i]);
            }
            else cnt_one++;
        // cout << 2 << endl;
        sort(vec.begin(), vec.end());
        LL res = (LL)n * cnt_one;
        // cout << res << endl;
        LL sum = 0;
        for (int i = 0; i < vec.size(); ++i) {
            sum += vec[i];
            res = min(res, sum * m + (--cnt_one) * n);
        }
        cout << res << endl;
    }
    return 0;
}

C. The Delivery Dilemma

题意: 给定a数组和b数组,对于1 ~ n的位置,如果选择a数组,那么花费为a数组中被选择的最大值;如果选择b数组,那么花费为b数组中被选择的累加值。求最小的答案

题解: 要让答案尽可能小,我们可以让a和b数组按照a数组从小到大排序,然后答案的情况必然包含在[min(max(a1, a2, …, an)), min(max(a1, a2, …an-1), bn), min(max(a1, a2, …, an-2), bn + bn-1)]。因此只需要排完序后扫描一遍即可。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;
PII point[N];

int main() {
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        scanf("%d", &n);
        for (int i = 0; i < n; ++i) cin >> point[i].first;
        for (int i = 0; i < n; ++i) cin >> point[i].second;
        sort(point, point + n);
        LL res = 1e9 + 10, sum = 0;
        for (int i = n - 1; i >= 0; --i) {
            res = min(res, max(sum, (LL)point[i].first));
            sum += point[i].second;
        }
        res = min(res, sum);
        printf("%lld\n", res);
    }
    return 0;
}

D. Extreme Subtraction

题意: 给定一个序列,长度为n,有2种操作,操作1可以使得从1 ~ k的数字都减一, 操作2可以使得从k ~ n的数字都减一1.问能否通过有限次操作,使得整个数列变为0.

题解: 最后使得整个数列变为0,可以规约为是否能够整个数列变为同一个数字,那么就是要让整个数列变为最小的那个数字。因此可以维护一个差分数组b,记录一下当前的位置需要减去多少,如果当前的a[i] < a[i + 1],那么就是要让 i + 1 ~ n减去a[i + 1] - a[i];如果a[i] > a[i + 1],那么就是要让1 ~ i减去a[i] - a[i + 1]。然后求个b数组的前缀即可知道每个数字最少要减去多少,与a[i]判断大小即可。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;
int a[N];
LL b[N];

int main() {
    cin >> T;
    while(T--) {
        memset(b, 0, sizeof b);
        cin >> n;
        for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
        for (int i = 1; i < n; ++i) {
            if (a[i] > a[i + 1]) {
                b[1] += a[i] - a[i + 1];
                b[i + 1] -= a[i] - a[i + 1];
            }
            else {
                b[i + 1] += a[i + 1] - a[i];
            }
        }
        int flg = 0;
        for (int i = 1; i <= n; ++i) {
            b[i] += b[i - 1];
            if (b[i] > a[i]) {
                flg = 1;
                printf("NO\n"); 
                break;
            }
        }
        if (!flg) printf("YES\n");
    }
    return 0;
}

E. Long Permutation

题意: 给定一个序列[1, 2, 3, …, n], 序列长度为1e5。有1e5个操作,分别为:操作1:1 l r,即计算序列区间[l, r]的区间和;操作2 :2 k,找出当前序列往后字典序第k大。1 <= k <= n。

题解: 由于1 <= k <= n,因此就算执行1e5次操作2,那么也只会是找序列的1e10个。而14! > 1e10,因此无论执行多少次操作,本质上只会改变序列的最后14位数字。所以,对于前缀和,可以直接暴力维护,然后对于每次操作2,求出最后14位数字的序列,然后暴力修改前缀和数组sum即可。

这里有一个trick,求[1, 2, 3, …, n]的后面第k个序列可以直接康托展开求解,但是如果求[2, 3, 1, …, n , n - 1]这样的序列后面的第k个是无法直接逆康托展开的。然而本题的序列是从[1, 2, 3, …, n]开始的,我们可以维护一个x,记录一下当前操作2需要求解的序列是相对于[1, 2, 3,…,n]的第几个,即每次都是x = x + k。然后求解最后14个数字的逆康托展开,可以先求出[1, 2, …, 14]往后的第k个,然后加上偏移量offset = n - 14。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T, q;
int a[N], c[100];
LL sum[N], fact[100];

int lowbit(int x) {
    return x & (-x);
}

// 单点修改
void add(int x, int y) {
    for (int i = x; i <= m; i += lowbit(i)) c[i] += y;  // 不断往父节点跳
}

int kth(int k) {
    int res = 0;  // res记录小于k的最后一个位置
    for(int i = m >> 1; i; i >>= 1)   // 以2进制逼近
        if(c[res + i] < k) {
            res += i;
            k -= c[res];
        }
    return res + 1;  // 因为返回的是前一个位置
}

// 逆康托展开: 2 -> [1, 4, 2, 3]
vector<int> decode(LL x) {
    m = 1;
    while(m <= min(14, n)) m <<= 1;  // m为大于n的第一个2的幂次方,这是利用了树状数组c数组处于2的幂次方时,c[i]相当于query(i)的性质,以此来优化二分
    memset(c, 0, sizeof c);
    vector<int> res;  // res存储答案序列
    for(int i = 1; i <= min(14, n); ++i) add(i, 1);  // 1表示没有使用,0表示使用过
    for(int i = min(14, n) - 1; i >= 0; --i) {  // 按照逆康托展开的公式处理
        int t = kth(x / fact[i] + 1);  // 类似二分的思想
        res.push_back(t); 
        add(t, -1);
        x %= fact[i];
    }
    return res;
}

int main() {
    cin >> n >> q;
    fact[0] = 1;
    for (int i = 1; i <= n; ++i) {
        a[i] = i, sum[i] = sum[i - 1] + i;
        if (i <= 14) fact[i] = fact[i - 1] * i;
    }
    int offset = 0;  // 计算偏移量
    if (n > 14) offset = n - 14;
    LL k = 0;
    for (int i = 1, op, l, r; i <= q; ++i) {
        scanf("%d", &op);
        if (op == 1) {
            scanf("%d%d", &l, &r);
            printf("%lld\n", sum[r] - sum[l - 1]);
        }
        else {
            LL x;
            scanf("%lld", &x);
            k += x;
            vector<int> res = decode(k); // 求出最后14的数字的逆康托展开
            for (int j = max(1, n - 13), idx = 0; j <= n; ++j) {
                sum[j] = sum[j - 1] + res[idx++] + offset;  // 每个数字实际上是res[idx]+offset
            }
        }
    }
    return 0;
}

F. Identify the Operations

题意: 给定一个长度为n的数组a,每次可以选择一个位置的数字删除,然后把它的左边或者右边元素加入到b数组的结尾。现在问从a数组构造b数组有多少种方案

题解: 对于b数组内的元素分析,b数组内的第一个数字,必然是第一次被加入的,因此需要扫描b数组来做判断。对于每一个b数组内的元素,如果当前元素被加入b数组,那么当前元素的相邻2个元素之一必然被删除,而这2个元素如果存在与还没有被扫描到的b数组中,那么不能被删除。所以对于每一个b[i],可以判断下其在a数组对应位置相邻两个位置的元素是否也处于b数组内,如果处于,那么这个数字不能被删除。每次只需要把可以删除的位置数目累成到方案中即可。

代码:

#include <bits/stdc++.h>

using namespace std;

int const N = 2e5 + 10, M = 998244353 ;
typedef long long LL;
typedef pair<int, int> PII;

int n, m, T;
int a[N], b[N], mp1[N], mp2[N];

int main() { 
    // freopen("in.txt", "r", stdin);
    cin >> T;
    while(T--) {
        for (int i = 1; i <= n; ++i) mp1[i] = mp2[i] = 0;
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), mp1[a[i]] = i;
        for (int i = 1; i <= m; ++i) scanf("%d", &b[i]), mp2[b[i]] = 1;
        LL res = 1;
        mp2[0] = 1, a[0] = a[n + 1] = 0;
        for (int i = 1; i <= m; ++i) {
            int k = 2;
            if (mp2[a[mp1[b[i]] - 1]]) k--;
            if (mp2[a[mp1[b[i]] + 1]]) k--;
            res = res * k % M;
            // cout << res << endl;
            if (!res) break;
            mp2[b[i]] = 0;
        }
        printf("%d\n", res);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值