Educational Codeforces Round 164 (Div. 2)
A - Painting the Ribbon
难度: ⭐⭐
解题思路
先看特殊情况, 如果m为1肯定不行, n小于等于k也不行; 我们可以换位思考, 如果Alice用了x种颜色, Bob想把其染为同一种颜色, 肯定要先找出这x种颜色中染色区域最多的那一种, 然后把其他区域的颜色换成该颜色, 这样才是最优策略, 所以相应的Alice就要阻止这种最优策略, 也就是使每种颜色的染色区域尽可能的少, 也就是有尽可能地多用颜色; 最后看看Bob用最优策略是否可以实现;
神秘代码
#include<bits/stdc++.h>
#define int unsigned long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e6 + 10, mod = 998244353, inf = 131;
typedef pair<int, int> PII;
int n, m, k;
bool check(){
if(n <= k) return false;
if(m == 1) return false;
int x = n / m + (n % m != 0);
if(k >= n - x) return false;
return true;
}
signed main(){
IOS;
int T;
cin >> T;
for(int i = 1; i <= T; i++){
cin >> n >> m >> k;
if(check()) cout << "YES" << endl;
else cout << "NO" << endl;
}
return 0;
}
B - Make It Ugly
难度: ⭐⭐
解题思路
根据题意我们可以得出漂亮数组的几个性质: 一是漂亮数组的第1个元素和最后1个元素一定相同, 我们设为x; 二是漂亮数组中不为x的元素一定是单独出现, 即其前后都是x;
根据这两个性质, 我们可以知道漂亮数组就是由若干个连续的值x的序列组成, 它们之间由不为x的元素分割: xxxaxxxxbxxxxcx; 而且要做的就是破坏这条性质, 也就是删除其中一个连续的值为x的序列, 所以找出其中最短的删去就可以了;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e6 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, k;
int p[N];
signed main(){
IOS;
int T;
cin >> T;
while(T--){
cin >> n;
for(int i = 1; i <= n; i++){
cin >> p[i];
}
int res = inf;
int num = 0;
bool f = true;
for(int i = 1; i <= n; i++){
if(p[i] != p[1]){
res = min(res, num);
num = 0;
}
else num++;
}
if(num) res = min(res, num);
if(res == n) res = -1;
cout << res << endl;
}
return 0;
}
C - Closest Cities
难度: ⭐⭐⭐
解题思路
一个比较明显的贪心问题, 对于两个长度相同的数a和b的乘积, 当a和b越相近时乘积越大; 根据这条性质我们可以先从左往右开始找到第一个两者数字不相同的位置, 此时这个位置上的数可以决定两者到底谁大谁小; 确定完之后, 我们之后的策略就是让小的数变大, 大的数变小; 毕竟往后的数再怎么变也不会改变两者的大小关系, 只会让a和b越接近;
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e6 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, k;
int p[N];
signed main(){
IOS;
int T;
cin >> T;
while(T--){
string s1, s2;
cin >> s1 >> s2;
int f = 0;
for(int i = 0 ; i < s1.size(); i++){
if(f == 0){
if(s1[i] > s2[i]) f = 1;
else if(s1[i] < s2[i]) f = 2;
}
else{
if(f == 1)
if(s1[i] > s2[i]) swap(s1[i], s2[i]);
else
if(s1[i] < s2[i]) swap(s1[i], s2[i]);
}
}
cout << s1 << endl << s2 << endl;
}
return 0;
}
D - Colored Balls
难度: ⭐⭐⭐⭐
解题思路
我感觉这题最难的是要想到用dp...而且题面也很难读懂... 因为n的范围是5000, 所以我们肯定没法遍历所有分组, 所以可以考虑递推; 递推的话要先找规律, 我们观察样例可以发现, 假设有一个分组中有x个数字, 其中最大的数字是maxn, x个数字总和为sum; 如果maxn大于等于其他数字的和(sum - maxn), 那么这个分组的值就是maxn; (也就是让其他数字都和maxn组合); 如果maxn小于其他数字的和, 那么答案就是sum / 2上取整; (就是两两组合);
根据这个规律我们发现maxn和sum可以直接代表整个分组, 也就是说我们不用具体知道这x个数都是多少; 而且题目范围也提示我们sum一定不大于5000; 那么我们就可以直接开一个二维的状态表示dp[i][j]表示最大值为i, 总和为j的分组有多少个;
这样会有一个问题, 状态转移时最多是O(n2)的复杂度, 但是我们有n, max, sum三个变量需要遍历, 所以要优化掉一维; 这里我们可以先把输入进行从小到大排序, 这样我们遍历到的p[i]就一定是此时的最大值, 之前所有处理好的状态的max都比p[i]小, 那么我们就不用管之前的max是多少了, 一股脑全加上就行, 开一个数组mf[i]表示在此之前sum为i的分组有多少个, 其更新为mf[j + p[i]] = mf[j + p[i]] + mf[j]; 这样就把max优化掉了; 最后的状态转移就是dp[p[i]][j + p[i]] = dp[p[i]][j + p[i]] + mf[j]; 其中j是遍历的sum;
最后我们遍历最大值和总和, 按照之前找的规律进行求解就可以了;
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef pair<int, int> PII;
const int N = 1e4 + 10, mod = 998244353;
int dp[N][N];
int mf[N];
int n;
int p[N];
signed main() {
cin >> n;
mf[0] = 1;
for(int i = 1; i <= n; i++) {
cin >> p[i];
}
sort(p + 1, p + 1 + n);
for(int i = 1; i <= n; i++){
for(int j = 5000; j >= 0; j--){
dp[p[i]][j + p[i]] = (dp[p[i]][j + p[i]] + mf[j]) % mod;
mf[j + p[i]] = (mf[j + p[i]] + mf[j]) % mod;
}
}
int res = 0;
for(int i = 0; i <= 5000; i++){
for(int j = 0; j <= 5000; j++){
if(i >= j - i){
res = (res + (dp[i][j] % mod * i) % mod) % mod;
}
else{
int x = (j + 1) / 2;
res = (res + (dp[i][j] % mod * x) % mod) % mod;
}
}
}
cout << res ;
return 0;
}
E - Chain Reaction
难度: ⭐⭐⭐⭐
解题思路
我们开一个数组h[i]表示第i个怪兽是否存活, 为1则存活; 我们攻击的目标就是一个连续的1的序列; 如果此时有x个连续的1的序列, 那么我们至少要攻击x次; 所以我们开一个数组f[i]表示用伤害为i攻击一次后还剩下几个连续的1的序列, f[0] = 1;
更新f[i]时我们发现是会有继承关系的, 如果没有血量为i的怪兽, 那么用i攻击和用i - 1攻击效果是一样的, 所以f[i] = f[i - 1]; 如果有血量为i的怪兽, 我们可以只处理这些怪兽, 因为其他怪兽的状态一定和f[i - 1]时是一样的, 所以可以在f[i - 1]的基础上进行修改; 如果第j个怪兽血量为i, 我们需要去检查第j - 1和j + 1个怪兽的状态, 如果这两个都存活, 那么该连续的1的序列会被一分为二, 连续的1的序列数量+1; 如果都不存活, 说明j自己就是一个连续的1的序列, 被杀后连续的1的序列会-1; 其他情况数量不变;
最后处理答案时, 因为f[i]是攻击一次后的情况, 如果攻击后还有存活的那么就得再攻击一次, 相当于f[i + i]; 所以循环时j += i
神秘代码
#include<bits/stdc++.h>
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e5 + 10, mod = 998244353, inf = 1e18;
typedef pair<int, int> PII;
int n, m, k;
int p[N], f[N], h[N];
vector<int> v[N];
signed main(){
IOS;
cin >> n;
for(int i = 1; i <= n; i++){
cin >> p[i];
m = max(p[i], m);
h[i] = 1;
v[p[i]].push_back(i);
}
f[0] = 1;
for(int i = 1; i <= m; i++){
f[i] = f[i - 1];
for(int x : v[i]){
if(h[x - 1] && h[x + 1]) f[i]++;
if(h[x - 1] == 0 && h[x + 1] == 0) f[i]--;
h[x] = 0;
}
}
for(int i = 1; i <= m; i++){
int res = 0;
for(int j = 0; j <= m; j += i){
res += f[j];
}
cout << res << ' ';
}
return 0;
}