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,ai−x),当然,你不能队耐久性为 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 x≤y时,我们减去的会比增加的要少,所以我们只能将门耐久性 ≤ 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}| k≤∣si−si+1∣。现在你可以交换行,你需要找到一个最大的 k k k。 -
解题思路
当我们看到 n n n的取值范围的时候,我们就应该要想到数位 D P DP DP,我们对这些行编号为 0 0 0~ n − 1 n-1 n−1,那么我们知道,对于两个编号为 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;
}

浙公网安备 33010602011771号