A.Holiday Of Equality(Codeforces 758A)
思路
国王想要让每个人民的福利均等,且只能增加福利。那么代价最小的策略莫过于让所有人的福利都等于当前福利最大的人的福利(假设有更优的策略,那么定有人的福利减少了,与题意不符)。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
int n, Max, ans, a[maxn];
int main() {
// freopen("data.txt", "r", stdin);
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
Max = max(Max, a[i]);
}
for(int i = 1; i <= n; i++) {
ans += (Max - a[i]);
}
printf("%d\n", ans);
return 0;
}
B.Blown Garland(Codeforces 758B)
思路
这题的巧妙之处在于,当字符串的前四个字符确定以后,其它所有的字符也就确定了(可以通过样例观察出来)。因此只要确定了前四个字符,就能解决本题。相比构造,判定要更容易些。也就是说,如果前四个字符确定了,那么就可以判断这四个字符是否是合法的。因此我们枚举前四个字符,判断这四个字符是否合法,若合法则输出答案。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
bool vis[maxn];
char s[maxn];
int n, a[maxn], b[5], ans[5];
// 在前四个字符合法的时候构造正确答案
void solve() {
for(int i = 1; i <= n; i++) {
int idx = (i - 1) % 4 + 1;
if(a[i] > 0) {
continue;
}
ans[b[idx]]++;
}
}
// 判断枚举的前四个字符是否合法
// 根据b数组判断a数组是否合法
bool ok() {
for(int i = 1; i <= n; i++) {
int idx = (i - 1) % 4 + 1;
if(a[i] != 0 && a[i] != b[idx]) {
return false;
}
}
return true;
}
// 用递归的方式枚举前四个字符
// 也就是填4个元素的b数组
// (当然也可以用四层循环)
void dfs(int cur) {
if(cur > 4 && ok()) {
solve();
}
for(int i = 1; i <= 4; i++) {
if(vis[i] == true) {
continue;
}
b[cur] = i;
vis[i] = true;
dfs(cur + 1);
vis[i] = false;
}
}
int main() {
// freopen("data.txt", "r", stdin);
scanf("%s", s + 1);
n = strlen(s + 1);
// 将字符编码有利于后面的枚举
for(int i = 1; i <= n; i++) {
if(s[i] == 'R') {
a[i] = 1;
} else if(s[i] == 'B') {
a[i] = 2;
} else if(s[i] == 'Y') {
a[i] = 3;
} else if(s[i] == 'G') {
a[i] = 4;
}
}
// 递归枚举
dfs(1);
// 输出答案
for(int i = 1; i <= 4; i++) {
printf("%d ", ans[i]);
}
puts("");
return 0;
}
思路
实际上,当字符串前四个字符都确定后,字符串每四个字符也就确定了。也就是说设字符串的第
k
个字符确定后,字符串上满足
我们不妨将
imod4=0
的位置称作位置
0
,以此类推还有位置
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
int idx[256], ans[4];
string s;
int main() {
cin >> s;
for(int i = 0; i < s.size(); i++) {
if(s[i] != '!') {
idx[s[i]] = i % 4;
} else {
ans[i%4]++;
}
}
cout << ans[idx['R']] << ' ';
cout << ans[idx['B']] << ' ';
cout << ans[idx['Y']] << ' ';
cout << ans[idx['G']] << ' ';
return 0;
}
C.Unfair Poll(Codeforces 758C)
思路
老师点名是有周期性规律的。其周期
T=2m(n−1)
。看到这个式子自然会想到当行是
1
的时候周期岂不是
于是对于
kmodT
次点名,我们可以暴力处理。对于
k/T
次点名,由于可以算出每个位置在一个周期中被访问多少次,所以我们可以通过遍历位置的方式来处理。
实现上就是填写矩阵
a
。其中
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 110;
int n, m, x, y;
ll k, T, Max, Min, G[maxn][maxn];
// 暴力填写矩阵
void brute(int k) {
int x = 1, y = 0, dx;
for(int i = 1; i <= k; i++) {
if(y == m) {
if(x == 1) {
dx = 1;
} else if(x == n) {
dx = -1;
}
x += dx;
y = 1;
} else {
y++;
}
G[x][y]++;
}
}
int main() {
// freopen("data.txt", "r", stdin);
cin >> n >> m >> k >> x >> y;
// 分类讨论,处理周期的整数倍点名
if(n == 1) {
T = m;
for(int j = 1; j <= m; j++) {
G[1][j] = k / T;
}
} else {
T = 2 * m * n - 2 * m;
for(int j = 1; j <= m; j++) {
G[1][j] = G[n][j] = k / T;
}
for(int i = 2; i <= n - 1; i++) {
for(int j = 1; j <= m; j++) {
G[i][j] = k / T * 2;
}
}
}
// 暴力处理剩余的点名
brute(k % T);
Max = -1;
Min = (1LL << 60);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
Max = max(Max, G[i][j]);
Min = min(Min, G[i][j]);
}
}
cout << Max << ' ' << Min << ' ' << G[x][y];
return 0;
}
D.Ability To Convert(Codeforces 758D)
思路
题目的意思是,将
k
划分成若干个部分,每部分表示
本题有贪心策略:对
k
从尾部向头部处理,每次取尽可能多的数字,将其转化成十进制数。
为什么这样是正确的呢?假设在处理到某步的时候最多能取
于是用不符合上述贪心策略的策略的话,肯定没有使用上述贪心策略更好。所以上述的贪心策略是最好的。
在实现上,我这里用的是双指针法(左开右闭区间),也有称其为“尺取法”的。双指针指向的区间表示当前十进制位对应的n进制数的区间。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int cnt, baseLen, numLen, head, tail;
ll nBase, nSub, ans, pro;
string sBase, sNum;
// 得到区间[l, r]内的子串
string getSub(int l, int r) {
int len = r - l + 1;
return sNum.substr(l, len);
}
// 将字符串转化成数字
ll getNum(string s) {
ll res;
stringstream ss(s);
ss >> res;
return res;
}
int main() {
cin >> sBase >> sNum;
baseLen = sBase.size();
numLen = sNum.size();
nBase = getNum(sBase);
sNum = " " + sNum;
pro = 1;
// 双指针法,双指针指向区间(head, tail]
for(tail = numLen; tail > 0;) {
// 直接取n同样长度的k的子串
head = max(tail - baseLen, 0);
nSub = getNum(getSub(head + 1, tail));
// 当子串大于或等于n时不合法,令子串减少一位
if(head + 1 < tail && nSub >= nBase) {
head++;
}
// 处理掉子串中的前缀零
while(head + 1 < tail && sNum[head+1] == '0') {
head++;
}
ans += getNum(getSub(head + 1, tail)) * pro;
// 累乘表示位权的变量
pro *= nBase;
tail = head;
}
cout << ans << endl;
return 0;
}
(其它题目略)