Codeforces Round #531 (Div. 3) A~F题解

A. Integer Sequence Dividing

  • 题意
    给你 1 1 1~ n n n的序列,需要你分成两个集合,使得两个集合的总和差值最小。

  • 解题思路
    我们知道,对于连续的四个元素,是一定能够凑成差值为 0 0 0的,设第一个元素为 a 1 a_1 a1,则接下来的为 a 1 + d , a 1 + 2 d , a 1 + 3 d a_1+d,a_1+2d,a_1+3d a1+d,a1+2d,a1+3d,不然想到第二个和第三个组合即可。那么也就是说,如果 n m o d    4 = 0 n\mod 4 =0 nmod4=0说明一定可行。我们又注意到这个等差数列的公差为 1 1 1,首项也为 1 1 1,即当出现连续的 3 3 3个数时,除了 1 , 2 , 3 1,2,3 1,2,3,它们的组合最大差值为 1 1 1。所以 1 , 2 , 3 1,2,3 1,2,3也是可行的,那么如果 n m o d    4 = 3 n\mod 4=3 nmod4=3也能凑成差值为 0 0 0,而对于其他的 1 , 2 1,2 1,2情况,我们最优就能达到差值为 1 1 1

  • AC代码

/**
  *@filename:A
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-27 20:49
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;

int n;
void solve(){
    if(n % 4 == 0 || n % 4 == 3){
        cout << "0" << endl;
    }
    else{
        cout << "1" << endl; 
    }
}
int main(){
    cin >> n;
    solve();
    return 0;
}

B. Array K-Coloring

  • 题意
    给你一个n个整数的序列 a a a,现有 k k k种颜色,你需要对这 n n n个元素进行染色,使得 k k k个元素都使用了,相同的颜色绘制的元素其值应该是不同的。

  • 解题思路
    根据题意,我们清楚,对于相同的元素我们只能用不同颜色去绘制,而当它的出现次数超过 k k k次,则绘制方案不可行,否则一定可行。那么对于不同元素我们是没有限制的,用相同元素或不同元素都可行。注意,我们需要将需要元素值按大小排序再循环染色,这样能保证相同元素染了不同的颜色。

  • AC代码

/**
  *@filename:B
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-27 20:54
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 5000 + 5;
const int P = 1e9+7;

int n,k,a[N],cnt[N];
int pos[N];
bool flag;
bool cmp(int i,int j){
    return a[i] < a[j];
}
void solve(){
    if(flag){
        cout << "NO" << endl;
    }
    else{
        cout << "YES" << endl;
        sort(pos + 1,pos + 1 + n,cmp);
        for(int i = 1; i <= n; ++ i){
            a[pos[i]] = i % k + 1;
        }
        for(int i = 1; i <= n; ++ i){
            cout << a[i] << " ";
        }
        cout << endl;
    }
}
int main(){
    cin >> n >> k;
    for(int i = 1; i <= n; ++ i){
        cin >> a[i];
        cnt[a[i]] ++;
        if(cnt[a[i]] > k){
            flag = true;
        }
        pos[i] = i;
    }
    solve();
    return 0;
}

C. Doors Breaking and Repairing

  • 题意
    给你 n n n个门的耐久性程度,现在你可以减少一个门 i i i的耐久性 x x x,使其成为 m a x ( 0 , a i − x ) max(0,a_i-x) max(0,aix),当然,你不能队耐久性为 0 0 0的门进行操作,对于 S l a v i k Slavik Slavik,她可以增加一个门 i i i的耐久性 y y y,使其成为 a i + y a_i+y ai+y。你们进行游戏,你先开始,当有一人不能进行操作的时候游戏结束,你两都是最优策略进行,问游戏结束后最大有多少耐久性为 0 0 0的门。
  • 解题思路
    不要将这题想的很难,我们知道如果 x > y x>y x>y我们总能将这 n n n个门全部变为 0 0 0,因为止不了损。而当 x ≤ y x\leq y xy时,我们减去的会比增加的要少,所以我们只能将门耐久性 ≤ x \leq x x的门进行操作,那么作为 S l a v i k Slavik Slavik她则会将这些耐久性 ≤ x \leq x x的门增加 y y y,所以最后就会有 ( a n s + 1 ) / 2 (ans + 1)/2 (ans+1)/2个门。
  • AC代码
/**
  *@filename:C
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-27 21:09
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;

int n,x,y;
int ans;
void solve(){
    //根据先手和后手决定。先手必定试想尽可能消灭当前能消灭的最大值。
    if(x > y){
        cout << n << endl;
    }
    else if(x <= y){
        cout << (ans + 1) / 2 << endl;
    }
}
int main(){
    cin >> n >> x >> y;
    int temp;
    for(int i = 1; i <= n; ++ i){
        cin >> temp;
        if(temp <= x){
            ans ++;
        }
    }
    solve();
    return 0;
}

D. Balanced Ternary String

  • 题意
    给你一个只包含 0 , 1 , 2 0,1,2 0,1,2的字符串,现在需要你进行最小修改操作使得其中的 0 , 1 , 2 0,1,2 0,1,2字符数量相同,且保证字典序最小。

  • 解题思路
    直接模拟即可,判断最大的是哪个字符,分情况讨论如下:

    • 0 0 0最多,如果 1 , 2 1,2 1,2需要分配,那么从后面开始修改。
    • 1 1 1最多,如果 0 0 0需要分配,那么从前面开始修改,如果 2 2 2需要分配,那么从后面开始修改。
    • 2 2 2最多,如果 0 , 1 0,1 0,1需要分配,那么从前面开始修改。

    据此,作出修改操作即可。

  • AC代码

/**
  *@filename:D
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-27 21:23
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;

int n,cnt[3];
string s;
void solve(){
    for(int i = 0; i < n; ++ i){
        cnt[s[i] - '0'] ++;
    }
    int maxx = 0,idx;
    for(int i = 0; i < 3; ++ i){
        if(cnt[i] > maxx){
            maxx = cnt[i];
            idx = i;
        }
    }
    int avg = n / 3;
    if(idx == 2){
        if(cnt[0] < avg){
            for(int i = 0; i < n && cnt[0] < avg && cnt[2] > avg; ++ i){
                if(s[i] == '2'){
                    s[i] = '0';
                    cnt[0] ++;
                    cnt[2] --;
                }
            }
            //再判断cnt[1]的情况。
            if(cnt[1] >= avg){
                for(int i = 0; i < n && cnt[0] < avg && cnt[1] > avg; ++ i){
                    if(s[i] == '1'){
                        s[i] = '0';
                        cnt[1] --;
                        cnt[0] ++;
                    }
                }
            }
            else{
                for(int i = 0; i < n && cnt[2] > avg && cnt[1] < avg; ++ i){
                    if(s[i] == '2'){
                        s[i] = '1';
                        cnt[1] ++;
                        cnt[2] --;
                    }
                }
            }
        }
        else{
            //说明0和2比1都多。
            for(int i = n - 1; i >= 0 && cnt[0] > avg && cnt[1] < avg; -- i){
                if(s[i] == '0'){
                    s[i] = '1';
                    cnt[1] ++;
                    cnt[0] --;
                }
            }
            for(int i = 0; i < n && cnt[2] > avg && cnt[1] < avg; ++ i){
                if(s[i] == '2'){
                    s[i] = '1';
                    cnt[1] ++;
                    cnt[2] --;
                }
            }
        }
    }
    else if(idx == 1){
        if(cnt[0] < avg){
            for(int i = 0; i < n && cnt[0] < avg && cnt[1] > avg; ++ i){
                if(s[i] == '1'){
                    s[i] = '0';
                    cnt[0] ++;
                    cnt[1] --;
                }
            }
            //再判断cnt[2]的情况。
            if(cnt[2] >= avg){
                for(int i = 0; i < n && cnt[0] < avg && cnt[2] > avg; ++ i){
                    if(s[i] == '2'){
                        s[i] = '0';
                        cnt[2] --;
                        cnt[0] ++;
                    }
                }
            }
            else{
                for(int i = n - 1; i >= 0 && cnt[1] > avg && cnt[2] < avg; -- i){
                    if(s[i] == '1'){
                        s[i] = '2';
                        cnt[2] ++;
                        cnt[1] --;
                    }
                }
            }
        }
        else{
            //说明0和1比2都多。
            for(int i = n - 1; i >= 0 && cnt[0] > avg && cnt[2] < avg; -- i){
                if(s[i] == '0'){
                    s[i] = '2';
                    cnt[2] ++;
                    cnt[0] --;
                }
            }
            for(int i = n - 1; i >= 0 && cnt[1] > avg && cnt[2] < avg; -- i){
                if(s[i] == '1'){
                    s[i] = '2';
                    cnt[2] ++;
                    cnt[1] --;
                }
            }
        }
    }
    else{
        //idx为0的情况。这个时候我们从后面替换1和2
        if(cnt[2] < avg){
            for(int i = n - 1; i >= 0 && cnt[0] > avg && cnt[2] < avg; -- i){
                if(s[i] == '0'){
                    s[i] = '2';
                    cnt[2] ++;
                    cnt[0] --;
                }
            }
            //再判断cnt[1]的情况。
            if(cnt[1] > avg){
                //说明要将1变成2.
                for(int i = n - 1; i >= 0 && cnt[1] > avg && cnt[2] < avg; -- i){
                    if(s[i] == '1'){
                        s[i] = '2';
                        cnt[2] ++;
                        cnt[1] --;
                    }
                }
            }
            else{
                //要将0变成1.
                for(int i = n - 1; i >= 0 && cnt[0] > avg && cnt[1] < avg; -- i){
                    if(s[i] == '0'){
                        s[i] = '1';
                        cnt[1] ++;
                        cnt[0] --;
                    }
                }
            }
        }
        else{
            //0和2都比cnt多。替换2从前面开始,替换0从后面开始。
            for(int i = 0; i < n && cnt[2] > avg && cnt[1] < avg; ++ i){
                if(s[i] == '2'){
                    s[i] = '1';
                    cnt[1] ++;
                    cnt[2] --;
                }
            }
            for(int i = n - 1; i >= 0 && cnt[0] > avg && cnt[1] < avg; -- i){
                if(s[i] == '0'){
                    s[i] = '1';
                    cnt[1] ++;
                    cnt[2] --;
                }
            }
        }
    }
    cout << s << endl;
}
int main(){
    cin >> n >> s;
    solve();
    return 0;
}

E. Monotonic Renumeration

  • 题意
    给你一个 a a a数组,需要你构建一个 b b b数组使得:

    • b 1 = 0 b_1=0 b1=0
    • 如果 a i = a j a_i =a_j ai=aj,那么 b i = b j b_i=b_j bi=bj
    • b i = b i + 1   o r   b i + 1 = b i + 1 b_i =b_{i+1} \space or\ b_i + 1=b_{i +1} bi=bi+1 or bi+1=bi+1

    输出构建 b b b数组的方案数。

  • 解题思路
    首先,我们发现对于 b b b数组,它是非递减的,所以如果 i < j , b i = b j i<j,b_i=b_j i<j,bi=bj,意味着 b i = b i + 1 = . . . = b j b_i=b_{i+1}=...=b_j bi=bi+1=...=bj。那么如果没有这个限制,那么下一个元素就有 2 2 2种选择。所以我们可以对 a a a数组进行分块,使得每个块中的元素若出现了二次以上,那么一定在这个块中。那么明显,在同一块中的 b b b只能取一个值,块的临界有 2 2 2中选择,那么总选择就是 p o w ( 2 , 块 数 − 1 ) pow(2,块数-1) pow(2,1)。我们设计算法对 a a a分块即可,可以记录每一个 i i i所对应的值最后出现的位置,然后我们从 1 1 1开始遍历,不断遍历此块知道到每个元素最后出现的位置即可。具体看 A C AC AC代码。

  • AC代码

/**
  *@filename:E
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-28 09:20
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 200000 + 5;
const int P = 998244353;

int n,a[N];
ll quick_pow(ll n,ll q){
    ll ans = 1;
    while(q){
        if(q & 1)ans = ans * n % P;
        n = n * n % P;
        q >>= 1;
    }
    return ans;
}
void solve(){
    //计算有多少块。
    //我们可以先记录在每个下标中出现的最后位置。
    map<int,int> p;
    int last[N];//last[i]表示在第i个元素中出现的最后位置。
    for(int i = n; i >= 1; -- i){
        if(!p[a[i]]){
            p[a[i]] = i;
        }
        last[i] = p[a[i]];
    }
    //然后我们计算有多少个块。
    int ans = 0,st = 1;
    while(st <= n){
        int ed = last[st];
        ans ++;
        //更新下一个块。
        for(int i = st; i <= last[ed] && i <= n; ++ i){
            //当跳出循环时,说明块中的最后一个元素就是最后一个元素。即块中出现的元素不会在下一个块中出现。
            ed = max(ed,last[i]);
        }
        st = ed + 1;
    }
    printf("%lld\n", quick_pow(2,ans - 1));
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; ++ i){
        scanf("%d", &a[i]);
    }
    solve();
    return 0;
}

F. Elongated Matrix

  • 题意
    给你一个 n × m n\times m n×m的矩阵,我们从上到下按列开始遍历,得到一个序列 s 1 , s 2 . . . s n m s_1,s_2...s_{nm} s1,s2...snm,而 k k k则为对于所有的 i i i k ≤ ∣ s i − s i + 1 ∣ k\leq |s_i-s_{i+1}| ksisi+1。现在你可以交换行,你需要找到一个最大的 k k k

  • 解题思路
    当我们看到 n n n的取值范围的时候,我们就应该要想到数位 D P DP DP,我们对这些行编号为 0 0 0~ n − 1 n-1 n1,那么我们知道,对于两个编号为 i , j i,j i,j的两个行,针对这两行的 k k k可以求出来,我们处理出这些值。然后我们可以枚举起点和状态,我们用 d p [ i ] [ j ] dp[i][j] dp[i][j]来表示状态为 i i i,且当前在 j j j行的最大 k k k值,考虑状态的转移,即从一行到另一个不在该状态中的行。最后考虑枚举终点,因为最后一行和第一行有联系,我们需要将这给计算出来即可。具体看 A C AC AC代码。

  • AC代码

/**
  *@filename:F
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-28 10:11
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 18,M = 1e4 + 5;
const int P = 1e9+7;
const int INF = 0x3f3f3f3f;

int n,m;
int a[N][M],kk[N][N];//kk[i][j]表示序号i的行和序号j的行的min(k)
//此题需要注意的细节就是无论行序号怎么交换,两个行之间的k是不变的。我们相当于是要凑最大的k。
//状压DP。
int dp[1 << N][N];//dp[i][j]就表示状态为i的时候且到达了j行的最大k。需要注意i的二进制上的1表示该行已经遍历了。
void init(){
    for(int i1 = 0; i1 < n; ++ i1){
        for(int i2 = i1 + 1; i2 < n; ++ i2){
            int k = INF;
            //遍历取最小值。
            for(int j = 0; j < m; ++ j){
                k = min(k,abs(a[i1][j] - a[i2][j]));
            }
            kk[i1][i2] = kk[i2][i1] = k;
        }
    }
}
void solve(){
    init();
    int sn = 1 << n;
    int ans = 0;
    //首先枚举起点。
    for(int st = 0; st < n; ++ st){
        memset(dp,0,sizeof(dp));
        dp[1 << st][st] = INF;//设为INF是为了保证能从这个点出发。
        //枚举状态。
        for(int s = 0; s < sn; ++ s){
            //枚举当前行。
            for(int i1 = 0; i1 < n; ++ i1){
                if(dp[s][i1]){
                    //枚举下一行。
                    for(int i2 = 0; i2 < n; ++ i2){
                        if(!(s & 1 << i2)){
                            //判断这个点是否访问过,如果没有,我们从这个点出发。获取这一行和上一行的最小差值的最大值。
                            dp[s | 1 << i2][i2] = max(dp[s | 1 << i2][i2],min(dp[s][i1],kk[i1][i2]));
                        }
                    }
                }
            }
        }
        //枚举终点。因为我们最后一行是接着上一行的,这也决定了k的值。
        for(int ed = 0; ed < n; ++ ed){
            int k = dp[sn - 1][ed];
            for(int j = 0; j + 1 < m; ++ j){
                k = min(k,abs(a[ed][j] - a[st][j + 1]));
            }
            ans = max(ans,k);
        }
    }
    printf("%d\n", ans);
}
int main(){
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; ++ i){
        for(int j = 0; j < m; ++ j){
            scanf("%d", &a[i][j]);
        }
    }
    solve();
    return 0;
}
posted @ 2022-03-26 16:48  unique_pursuit  阅读(29)  评论(0)    收藏  举报