【赛后小结】Codeforces Round #630 (Div. 2)

比赛相关信息

比赛信息

比赛名称: Codeforces Round #630 (Div. 2)
比赛地址: Codeforce


部分题解与小结

B - Composite Coloring

小评

数论题,看了好久想了好多方法,都感觉不太对。后期看完 \(C\) ,感觉这题简单一点,就猜了一下规律码了一发,结果过了……补题发现这是一道极其数论的题目,要到达正确思路只有从规律出发推导,或者瞎猜,总之是非常魔幻的一道题。

题意

给出 \(N(1\le N\le 10^3)\) 个合数,且保证 \(4\le A_i\le10^3\) ,你至多可以将这些数分为 \(11\) 个组,使得:

  • 组内所有数字两两不互质(即 \(\tt gcd > 1\) );

多组样例,保证 \(\sum N \le 10^4\) 。输出任意一种分组方式。

思路

我们知道数学规律:

一个合数 \(X\) 的最小质因数小于等于 \(\sqrt X\)

由题中给出的范围,我们得到可能的最小质因数 \(\sqrt {1000} \ge 33\) ,而 \(33\) 以内的质数恰好只有 \(11\) 个,所以至此,原题条件转化为:组内所有数字最小质因数相等,得解。

本场比赛代码写的较丑,主要看思路orz。

AC代码

点击查看代码
#define int LL
const int N    = 1e6 + 7;
int a[N], ans[N];
void Solve() {
    int n; cin >> n; VI mp[105];
    FOR (i, 1, n) {
        cin >> a[i];
        for (int j = 2; j * j <= a[i]; ++ j) {
            if (a[i] % j == 0) {
                mp[j].pb(a[i]);
                break;
            }
        }
    }
    
    int cnt = 0;
    FOR (i, 1, 104) {
        if (!mp[i].empty()) {
            ++ cnt;
            for (auto it : mp[i]) ans[it] = cnt;
        }
    }
    
    cout << cnt << endl;
    for (int i = 1; i <= n; ++ i) cout << ans[a[i]] << " ";
    cout << endl;
}





C - K-Complete Word

小评

初见以为是一道很难的字符串题,由于自己对于字符串的理解不深,所以尝试找规律,稍加尝试结果又过了……赛后补题才后知后觉,虽然自己在书写代码时没有进行严格的证明,但却奇迹般的符合题意,可以说是相当巧妙了。

题意

给出一条长度为 \(N\) 的字符串 \(S\) ,给出周期 \(T\) ,规定操作如下:

  • 选择 \(S\) 中的任意位置,将这一位修改成任意字母;

输出最少的操作次数,使得操作后的 \(S\) 是周期为 \(T\) 的回文字符串。多组样例,满足 \(\sum N \le 2 * 10^ 5\)

思路

根据题意,我们发现,只需要修改 \(S\) 的前 \(K\) 位,使得每一位与其周期位、对称位均相等,即可使得整个字符串满足题意。

关于这一点的另一个解释是(来自官方题解):满足题意的字符串同样满足——对于每一个周期,其也是回文字符串。

而另一点观察是,通过以上的方式修改,每个位置至多只会被修改一次,故不会发生前后修改矛盾的情况(例如,第 \(i\) 位在这一轮被修改了,在下一轮不会再被修改回去),即我们的每一次修改都是必要的、有效的。所以我们有思路:对于前 \(K\) 位,找到其与其周期位、对称位里出现次数最多的字母 \(x\) ,并将这些位置全部修改为 \(x\)

AC代码

点击查看代码
#define int LL
string s;
int n, k, num; 

void Solve() {
    cin >> n >> k >> s;
    int ans = 0;
    FOR (i, 0, k - 1) {
        int num = 0, a[26] = {};
        for (int t = i; t <= n - 1; t += k) { //找到所有需要修改的位置
            ++ a[s[t] - 'a'];
            ++ num;
            if ((n - t - 1) % k != i) {
                ++ a[s[n - t - 1] - 'a'];
                ++ num;
            }
        }
        int m = 0, mm = 0;
        FOR (j, 0, 25) { //找到出现次数最多的字母
            if (a[j] > m) {
                m = a[j];
                mm = j;
            }
        }
        ans += num - m;
        char x = 'a' + mm;
        for (int t = i; t <= n - 1; t += k) {
            s[t] = s[n - t - 1] = x; //全部进行修改
        }
    }
    cout << ans << endl;
}





D - Walk on Matrix

小评

虽然题目又是 \(\tt DP\) 又是位运算的,但是这道题本质上并不是很难,大胆的寻找规律即可。

题意

给出一个 \(N*M(1\le N,M\le 500)\) 的矩阵,每一个格子上都有一个数字 \(A_{i,j}(0 \le A_{i, j} \le 3 * 10^5)\) 。现在要从左上角 \((1,1)\) 移动到右下角 \((N,M)\) ,规定移动操作如下:

  • 只能向下或向右移动;
  • 每达到一个格子,玩家的分数 \(Score\) 会被更新为 \(Score \& A_{i,j}\)

为了使得到达 \((N,M)\) 时自己的分数达到最大,Bob使用 \(\tt DP\) 思想设计了一个程序,如下:

截图

然而,这个程序并不正确,现在,给出一个数字 \(K(0\le K \le 10^5)\) ,你需要构建一个合乎规定的矩阵,使得Bob的程序与正确答案恰好相差 \(K\)

思路

观察Bob的程序可以得知,其错误的原因在于上一步的最大值并不一定是最优的,即对于位运算 \(\&\) ,局部最优不能构成全局最优。

第一步:假设。矩阵的大小在开始时并无法确定,我们约定,构建的矩阵为\(\begin{bmatrix} X_{1,1} & X_{1,2} & \cdots & X_{1,M-1} & X_{1,M} \\ \vdots & \vdots & \ddots & \vdots & \vdots \\ X_{N-1,1} & X_{N-1,2} & \cdots & X_{N-1,M-1} & 0 \\ X_{N,1} & X_{N,2} & \cdots & X_{N,M-1} & X_{N,M} \end{bmatrix}\) ,Bob输出的矩阵为 \(\begin{bmatrix} T_{1,1} & \cdots & \cdots & T_{1,M-1} & T_{1,M} \\ \vdots & & \ddots & \vdots & \vdots \\ T_{N-1,1} & \cdots & \cdots & T_{N-1,M-1} & 0 \\ T_{N,1} & \cdots & T_{N,M-2} & Y_{N,M-1} & Y_{N,M} \end{bmatrix}\) ,正解输出的矩阵为 \(\begin{bmatrix} T_{1,1} & \cdots & \cdots & T_{1,M-1} & T_{1,M} \\ \vdots & & \ddots & \vdots & \vdots \\ T_{N-1,1} & \cdots & \cdots & T_{N-1,M-1} & 0 \\ T_{N,1} & \cdots & T_{N,M-2} & Z_{N,M-1} & Z_{N,M} \end{bmatrix}\)

第二步:分析。注意到 \(Y_{N-1,M}\)\(Z_{N-1,M}\) 的位置被赋值为了 \(0\) ,这是为了保证答案只能由 \(Y_{N,M-1}\)\(Z_{N,M-1}\) 传递得到,去除了不必要的讨论。

根据Bob的错误,我们假设错误只发生在最后一步 \(\tt max\)\(Y_{N,M-1}>Z_{N,M-1}\) ,Bob的程序显然会选择 \(Y_{N,M-1}\) ,我们只需要使得选择 \(Z_{N,M-1}\) 时答案更优即可。

再进一步分析 \(Y_{N,M-1}\)\(Z_{N,M-1}\) 的来历,我们发现,它们一个是由 \(T_{N,M-2}\&X_{N,M-1}\) 得到,一个是由 \(T_{N-1,M-1}\&X_{N,M-1}\) 得到,而要使得Bob的程序错误,只需要使得 \(T_{N,M-2}\)\(T_{N-1,M-1}\) 不同,除此之外没有别的条件。

我们考虑收敛矩阵,发现最小只需要 \(2*3\) 的矩阵,即可使得 \(T_{N,M-2}\)\(T_{N-1,M-1}\) 不同。

第三步:构建。至此,我们进一步约定,构建的矩阵为 \(\begin{bmatrix} \mathcal A & \mathcal B & 0 \\ \mathcal D & \mathcal E & \mathcal F \end{bmatrix}\) ,Bob输出的矩阵为 \(\begin{bmatrix} a & b & 0 \\ d & e & f \end{bmatrix}\) ,正解输出的矩阵为 \(\begin{bmatrix} A & B & 0 \\ D & E & F \end{bmatrix}\)

由于需要让答案恰好相差 \(K\) ,我们不妨使 \(f=0,F=K\) ,那么可以得到 \(\mathcal F=K\)

那么可以得到 \(e=X,E=K\) ,其中,\(X\) 这个数不能与 \(K\) 在某一位上均为 \(1\) 。在这里,出题人简化了 \(X\) 的取值过程:根据所给定的取值,我们只需要取 \(2^{17}=131072\) ,就能保证对于任意的 \(K\)\(X\) 均满足条件。

这里出题人如果没有做简化的话,\(X\) 的取值可以为 \(2^{log_2^k+1}\) ,即 \(1\) 后面 \(k+1\)\(0\) ,满足上述条件。

同理,可以得到一组可行解 \(\mathcal {E=A}=X+K,\mathcal B=X,\mathcal D=K\) ,直接输出即可。

AC代码

点击查看代码
void Solve() {
	int t = (1 << 17);
	int k; cin >> k;
	cout << 2 << " " << 3 << endl;
	cout << t + k << " " << t << " " << 0 << endl;
	cout << k << " " << t + k << " " << k << endl;
}



文 / WIDA
2022.05.15 成文
首发于WIDA个人博客,仅供学习讨论


posted @ 2022-05-15 20:31  hh2048  阅读(25)  评论(0编辑  收藏  举报