CodeTON Round 6 (Div. 1 + Div. 2, Rated, Prizes!) 更新ing

发布时间 2023-09-21 11:26:01作者: C2-103

A. MEXanized Array

题意
给你三个数\(n\)\(k\)\(x\),让你求出能否构造出mex为\(k\),且所有数字都不超过\(x\),大小为\(n\)的数组。

线索1
如果有存在-1情况,先想啥时候不可能,如果能一下子找到-1的情况,这个题会很简单,因为可以的情况反推过去很容易,如果特判复杂就想想是不是诈骗规律了,这个题就特判,然后贪心做即可。
点击查看代码
void solve() {
    int n, k, x;
    cin >> n >> k >> x;
    if(k > n || x < k - 1) {
        cout << "-1\n";
        return;
    }
    int sum = 0;
    for(int i = 0; i <= k - 1; ++i) {
        sum += i;
    }
    //剩余n - k个数
    if(x - (k - 1) <= 1) { 
        //cout << n << " " << k << " " << x << "\n";
        for(int i = 1; i <= n - k; ++i) sum += k - 1;
    }else {
        for(int i = 1; i <= n - k; ++i) sum += x;
    }
    cout << sum << "\n";
}

B. Friendly Arrays

题意
你有两个数组\(a\)\(b\),长度大小分别为\(n\)\(m\) ,你可以操作任意次,从\(b\)里面任意选择一个数|上\(a\)数组所有数,最后需要求出\(a_i\)异或的可能最小值,跟最小值。
线索

线索1
想想|的性质,以及^的性质
线索2
n为奇数时候,明显,尽可能的让更多的位为1,那么异或出来位置都是1,即是最大值。n为偶数,尽可能的让更多的位为1,那么异或出来位置都是0,即是最小值。
点击查看代码
void solve() {
    int n, m;
    cin >> n >> m;
    vector<int> a(n), b(m);
    rep(i, 0, n - 1) cin >> a[i];
    int ok = 0;
    rep(i, 0, m - 1) cin >> b[i], ok |= b[i];
    int mx = 0, mn = 0;
    if(n & 1) {
        for(auto& x : a) {
            mx ^= (ok | x);
            mn ^= x;
        }
    }else {
        for(auto& x : a) {
            mx ^= x;
            mn ^= (ok | x);
        }
    }
    cout << mn << " " << mx << "\n";
}

C. Colorful Table

题意
给你一个数字都\(\leq k\)的大小都为\(n\)的数组,按照题意构造出一个二维数组\(b\),问你从\(1-k\)每个数字,包含所有的\(i\)的最小矩阵是多大。

线索

线索1
尝试在构造出的矩阵中,找到最可能出现的每个数字可能出现的边界位置
线索2
我们可以发觉一个规律,边界出现位置,是左边大于等于他的数最远的一个位置,右边反之。答案即是r - l + 1的二倍
点击查看代码
void solve() {
    int n, k;
    cin >> n >> k;
    vector<int> a(n + 1);
    vector<vector<int> > pos(k + 1);
    rep(i, 1, n) {
    	cin >> a[i];
    	pos[a[i]].push_back(i);
    }
    vector<int> ans(k + 1, 0);
    int l = 1e6, r = 0;
    for(int i = k; i >= 1; --i) {
        if(pos[i].size() == 0) continue;
    	for(auto&x : pos[i]) {
    		l = min(l, x);
    		r = max(r, x);
    	}
        //cout << l << " " << r << "\n"; 
    	ans[i] = (r - l + 1) * 2;
    }
    rep(i, 1, k) cout << ans[i] << " \n"[i == k];
    //cout << "\n";
}

summary
看到构造题,大部分都是规律+模拟,往找规律方向想,这个题提供了找到如何查找一个数组中所有数,比自己大的数的左右边界解决skill,限制了一定了数据范围前提。

D. Prefix Purchase

题意
你有一个初始化全为\(0\)大小为\(n\)的数组\(a\),总体力为\(k\),有一个消耗列表数组\(c\),每次你可以消耗\(c_i\)的体力,在数组\(a\)\([1,i]\)这个区间都加上\(1\),操作任意次,体力不够就不能操作,尽可能让\(a\)字典序最大。

线索1
操作次数肯定越多越好,很显然第一个思路就是一直操作最小的消耗ci
线索2
观察样例2,使用2次3,跟一次3一次4,其实本质是将一次操作更换了成了4,在保证次数不变的前提下。
线索3
保证总次数不变的情况下,我们一直往后找次小值的最右边的位置,这里需要处理一个后缀最小值,找到以后,看是否影响替换次数,不影响,我们就用剩下的体力接着替换,同时记得差分记录。最后输出原数组即可
点击查看代码
void solve() {
    int n, k;
    cin >> n;
    vector<int> c(n + 2, 0);
    map<int, int> mp; //记录每个数最后出现的位置 找次小值边界用
    rep(i, 1, n) {
        cin >> c[i];
        mp[c[i]] = i;
    }
    cin >> k;
    vector<int> suf(n + 2); //后缀最小值
    suf[n] = c[n];
    for(int i = n - 1; i >= 1; --i) {
        suf[i] = min(c[i], suf[i + 1]);
    }
    int mn = 1; //找出最小值位置
    rep(i, 1, n) {
        if(c[i] <= c[mn]) mn = i;
    }
    int cnt = k / c[mn]; //总次数
    int left = k % c[mn]; //剩下的体力
    int pos = mn;
    vector<int> d(n + 2, 0); //差分数组
    d[pos] = cnt; //最小值位置先加上cnt次
    for(int i = pos + 1; left && i <= n; ++i) {
        //如果不是最后位置的次大值 或者 不是次大值 就跳过
        if(suf[i] != c[i] || mp[c[i]] != i) continue;
        //更换当前的次大值体力不够用了
        if(c[i] - c[pos] > left) break; 
        //看能替换多少次过来
        int add = min(left / (c[i] - c[pos]), d[pos]);
        d[pos] -= add; //上一个位置减回去
        d[i] += add;
        left -= add * (c[i] - c[pos]);
        pos = i; //更新位置 继续往后找
    }
    //rep(i, 1, n) cout << d[i] << " \n"[i == n];
    //差分还原
    for(int i = n - 1; i >= 1; --i) d[i] += d[i + 1];
    rep(i, 1, n) cout << d[i] << " \n"[i == n];
}

summary
贪心题,注意模拟的细节,d提供了找最右次大值的skill