Codeforces Round #531 (Div. 3) 全题解

A-Integer Sequence Dividing

题意:就是让你将1-n分成两部分,要求|sum(A) - sum(B)|最小,问最小是多少。

解题思路:4个连续的数肯定可以组成sum(A) == sum(B),1、2、3可以组成1+2==3,这样直接将n%4如果等于0或者等于3都可以组成sum(A) == sum(B),否则很容易想到差为1。

#include 
using namespace std;
typedef unsigned long long ll;
typedef pair<ll,ll> P;

const int maxn = 1e5+100;

int main() {
    //freopen("1.in", "r", stdin);
    ll n;
    scanf("%lld", &n);
    if(n % 4 == 0 || n %4 == 3) {
        puts("0");
    } else {
        puts("1");
    }
    return 0;
}




B-Array K-Coloring

题意:n个数k种颜色,三个要求

  • 每个数必须被涂山一种颜色
  • k个颜色,每个至少被用到一次
  • 相同的数字必须填不同的颜色
    要求你输出一种涂色的方案。

解题思路:先判断是否可以保证每个颜色都用到,是否相同的数字可以都涂上不同的颜色。然后把每一种颜色独立出来,然后分类将颜色从1到k开始循环使用,输出就行了。

#include 
using namespace std;
typedef unsigned long long ll;
typedef pair<ll,ll> P;

const int maxn = 10000;
int n, t, num[maxn], num1[maxn];
map <int, int> maps;
vector <int> ve[maxn];

int main() {
    //freopen("1.in", "r", stdin);
    scanf("%d%d",&n,&t);
    int Max = 0;
    for(int i=1;i<=n;i++) {
        scanf("%d",&num[i]);
        num1[i] = num[i];
        maps[num[i]]++;
        Max = max(Max, maps[num[i]]);
    }

    if(Max > t || n < t) {
        puts("NO");
        return 0;
    }

    sort(num1+1, num1+n+1);
    int tot = 1;
    for(int i=1;i<=n;i++) {
        ve[num1[i]].push_back(tot++);
        if(tot > t)
            tot -= t;
    }

    puts("YES");
    for(int i=1;i<=n;i++) {
        printf("%d ", ve[num[i]][ve[num[i]].size()-1]);
        ve[num[i]].pop_back();
    }
    return 0;
}


C-Doors Breaking and Repairing

题意:n扇门,每个门有个生命值Ai,小明要破坏门,有个伤害值x,小美要保护门,有个回复值y,两人轮流行动,当一扇门被完全破坏之后小美无法再回复,回合数无限,现在要让小明破坏最少的门,问最少小明可以破坏多少个门。

解题思路:如果x>y那么n扇门全被破坏,否则找出生命值小于x的门,假设有c扇,可以最少被破坏的就是(c+1)/2。其实就是可以想象在c扇门中小明从前开始破坏,小美从后开始回复。

#include 
using namespace std;
typedef unsigned long long ll;
typedef pair<ll,ll> P;

const int maxn = 110;
int num[maxn], cnt, n, x, y;
vector <int> ve;

int main() {
    //freopen("1.in", "r", stdin);
    scanf("%d%d%d",&n, &x, &y);
    for(int i=1;i<=n;i++) {
        scanf("%d", &num[i]);
        if(num[i] <= x) {
            ve.push_back(num[i]);
            cnt++;
        }
    }

    if(x > y) {
        printf("%d", n);
        return 0;
    }
    
    printf("%d", (cnt+1)/2);
    return 0;
}


D-Balanced Ternary String

题意:现在有一个字符串s1,里面只包含字符0、1、2,现在你需要用最少的替换次数将s1变成s2,要求s2中0、1、2字符数目相等并且字典序最小。

解题思路:先算出0、1、2分别个数为cnt,

  • 先从前往后如果2的个数大于cnt,0的个数小于cnt,将2转换成0;然后如果2的个数大于cnt,1的个数小于cnt,将2转换成1;如果1的个数大于cnt,0的个数小于cnt,将1转换成0。
  • 在从后往前如果0的个数大于cnt,2的个数小于cnt,0转换成2;如果0个数大于cnt,1个数小于cnt,0转换成1;如果1个数大于cnt,2个数小于cnt,1转换成2。
#include 

using namespace std;
typedef unsigned long long ll;
typedef pair<ll, ll> P;

const int maxn = 3e5 + 100;

int n, each, cnt[300];
char s[maxn];

void change(char a, char b) {
    cnt[a]++;
    cnt[b]--;
}

int main() {
    //freopen("1.in", "r", stdin);
    scanf("%d%s", &n, s);
    int len = strlen(s);
    for (int i = 0; i < len; i++)
        cnt[s[i]]++;
    each = n / 3;

    //2 to 0 1
    for (int i = 0; i < len; i++) {
        if (cnt['2'] <= each) break;
        if (s[i] == '2') {
            if (cnt['0'] < each) {
                s[i] = '0';
                change('0', '2');
            } else if (cnt['1'] < each) {
                s[i] = '1';
                change('1', '2');
            }
        }
    }


    //1 to 0
    for (int i = 0; i < len; i++) {
        if (cnt['1'] <= each) break;
        if (s[i] == '1') {
            if (cnt['0'] < each) {
                s[i] = '0';
                change('0', '1');
            }
        }
    }


    //0 1 to 2
    for (int i = len - 1; i >= 0; i--) {
        if (s[i] != '2' && cnt['2'] < each) {
            if (cnt[s[i]] > each) {
                change('2', s[i]);
                s[i] = '2';
            }
        }
    }


    //0 to 1
    for (int i = len - 1; i >= 0; i--) {
        if (cnt['0'] <= each) break;
        if (s[i] == '0') {
            if (cnt['1'] < each) {
                change('1', '0');
                s[i] = '1';
            }
        }
    }


    printf("%s", s);
    return 0;
}


E - Monotonic Renumeration

题意:现在有一个数列a,经过符合三种条件的映射可以得到数列b

  • b(1= = 0
  • 如果a(i) == a(j) 则 b(i) == b(j)
  • b(i) == b(i+1) 或 b(i) +1 == b(i+1)
    问可以形成多少种数列b,方案mod 998244353。

解题思路:假如有一个数列a为1,2,3,4,5,6,7,8,1,那么形成的数列b只有1种(全0),也就是说两个相同的数字中间无论包含多少数字都只有一种,这就很像odt中的区间合并了,然后模仿了一下odt的区间合并就出来了。当然数字太大需要离散化一下。

#include 
using namespace std;
typedef long long ll;
const int maxn = 2e5+100;
const int mod = 998244353;

int n, a[maxn], last[maxn], pre[maxn];

struct P {
    int l, r;

    P(int L, int R = 0):
            l(L), r(R){};

    bool operator <(const P& x) const {
        return l < x.l;
    }
};

vector <int> ve;
set <P> tot;

void init() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%d",&a[i]);
        ve.push_back(a[i]);
    }

    //离散化
    sort(ve.begin(), ve.end());
    ve.erase(unique(ve.begin(), ve.end()), ve.end());

    for(int i=1;i<=n;i++) {
        a[i] = int(lower_bound(ve.begin(), ve.end(), a[i]) - ve.begin()) + 1;
        tot.insert(P(i, i));
    }
}

void deal() {
    for(int i=1;i<=n;i++) {
        if(!pre[a[i]]) pre[a[i]] = i;
        last[a[i]] = i;
    }
    for(int i=0;i<ve.size();i++) {
        int pos = i+1;
        int l = pre[pos];
        int r = last[pos];
        //区间合并

        set<P> :: iterator iter1, iter2, iter3;
        iter1 = tot.upper_bound(P(l));
        iter1--;
        iter2 = tot.upper_bound(P(r, n+1));
        r = (--iter2)->r;
        iter2++;
        l = iter1->l;
        tot.erase(iter1, iter2);
        tot.insert(P(l, r));
    }
}

ll quick_pow(ll x, ll cnt) {
    x %= mod;
    ll ans = 1;
    while(cnt) {
        if(cnt&1) ans *= x;
        x *= x;
        x %= mod;
        ans %= mod;
        cnt >>= 1;
    }
    return ans;
}

int main() {
    //freopen("1.in", "r", stdin);
    init();
    deal();
    int cnt = tot.size();
    ll ans = quick_pow(2, cnt-1);
    printf("%lld", ans);
    return 0;
}


F - Elongated Matri

解题思路:队友告诉我这是一个旅行商问题,但是我并不会,所以看了看了大佬怎么写的,然后自己写了一遍。首先看n和m的范围,n是16这就很明显的一个状压的标志了。整体思路就是状压+记忆化,因为复杂度的原因需要预处理每两行之间对应差的最小值。然后枚举第i行后面需要移动的所有状态。用dp[i][j]表示第i行j状态(如果j二进制状态位置为k的地方是0代表第k行需要移动到i行后面),然后递归到需要移动的第k行。

#include 
using namespace std;
const int maxn = 20;
const int maxm = 1e4+100;

int num[maxn][maxm], va[maxn][maxm], va2[maxn][maxm], dp[maxn][(1<<17)];
int n, m;

void init() {
    scanf("%d%d",&n, &m);
    for(int i=0;i<n;i++) {
        for (int j = 0; j < m; j++) {
            scanf("%d", &num[i][j]);
        }
    }

    for(int i=0;i<n;i++) {
        for(int j=0;j<n;j++) {
            va[i][j] = va2[i][j] = INT_MAX;
            for(int k=0;k<m;k++){
                va[i][j] = min(va[i][j], abs(num[i][k] - num[j][k]));
                if(k != 0)
                    va2[i][j] = min(va2[i][j], abs(num[i][k] - num[j][k-1]));//拼接成一个数组之后,拼接中间产生的差值
            }
        }
    }
}

int row;

int dfs(int pre, int state) {
    if(state == ((1<<n)-1)) return va2[row][pre];//无法再交换行的未知
    if(dp[pre][state] != -1) return dp[pre][state];//这个状态曾经被查找过

    dp[pre][state] = 0;
    for(int i=0;i<n;i++) {
        if(state & (1<<i))continue;
        dp[pre][state] = max(dp[pre][state], min(dfs(i, (state|(1<<i))), va[pre][i]));//递归
    }

    return dp[pre][state];
}

int main() {
    //freopen("1.in", "r", stdin);
    init();
    int ans = 0;
    for(row=0; row<n; row++) {
        memset(dp, -1, sizeof(dp));
        ans = max(ans, dfs(row, (1<<row)));//后面可以交换的所有状态
    }
    printf("%d", ans);
    return 0;
}





比赛心得:
  1. A题看了半天没啥思路,找到思路后犯了智障,然后WA两发,心态有点崩。然后B题倒是很容易就看懂了,很快敲出来了,忘记注释feopen,忘记输出“YES”又WA两发,心态崩完。C题写到后面明明就是一个公式的问题,自己脑残写了模拟,然后写歪了RE一发,虽然还是用模拟怼过了但是把脑袋给写晕了。D题很快找到思路,然后模拟过了,结果忘记循环中break居然过了44组测试样例,然后被hack。E题在赛场上读错了题,自己都不知道自在瞎搞什么。
  2. 每一个题都很快就有思路,在写的过程中各种犯错,脑子铁得不行。太久没打cf了,节奏不对,多打几场就好了。

你可能感兴趣的:(比赛)