2022SHCPC
2022SHCPC
作为新的起点!srds还有好多没写的,
A Another A+B Problem
Description
给定一个等式,形式为 ab+cd=ef 。 对于每个数,给定一个颜色。绿色表示这个数的位置正确;紫色表示这个数在答案等式中但位置不正确;黑色表示这个数不在答案等式中,且答案等式该位置上不是该数。
Solution
注意到至多只有6个未知数,而每个数有10种可能。枚举的复杂度是\(O(10^6)\),可行。
具体如何判断合法?
首先需要对于每一个特定的数字 i(0~9),记录它在输入等式中绿色和紫色格子里出现的次数\(num_i\)和它是否在黑色格子出现过\(ban_i\)。
合法的答案等式应该满足下列条件:
① 对于每一个数字(0~9),设其在准答案等式中出现\(tmpnum_i\)次,那么应该满足\(tmpnum_i>=num_i\)
② 对于每一个在黑色格子中出现的数字,要满足\(tmpnum_i==num_i\)
③ 对于所有的位置,若该位置的颜色是紫色或黑色,那么该位置上的数不能与输入等式中的相同。
④ 判断数值是否能使等式成立。
Code
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e6 + 5, M = 12;
char s1[M], s2[M], s3[M];
int s4[M],ansnum,ans[N][M];
int num[M],tmp[M],tmpnum[M];
bool ban[M];
bool judge() {
//首先判断数目是否合法
for (int i = 0;i < 10;++i) {
if (ban[i] && tmpnum[i] != num[i])return 0;
if (!ban[i] && tmpnum[i] < num[i])return 0;
}
//判断数所在的位置是否合法
for (int i = 0;i < 6;++i) {
if (s3[i] != 'G' && tmp[i] == s4[i])return 0;
}
//判断等式是否成立
int x1 = tmp[0] * 10 + tmp[1];
int x2 = tmp[2] * 10 + tmp[3];
int x3 = tmp[4] * 10 + tmp[5];
if (x1 + x2 != x3)return 0;
return 1;
}
void dfs(int p) {
if (p == 6) {
if (judge()) {
++ansnum;
for (int i = 0;i < 6;++i)
ans[ansnum][i] = tmp[i];
}
return;
}
if (s3[p] == 'G') {
++tmpnum[s4[p]];
tmp[p] = s4[p];
dfs(p + 1);
--tmpnum[s4[p]];
return;
}
for (int i = 0;i < 10;++i) {
if (ban[i] && tmpnum[i] >= num[i])continue;
if (s3[p] != 'G' && i == s4[p])continue;
++tmpnum[i];
tmp[p] = i;
dfs(p + 1);
--tmpnum[i];
}
}
int main() {
cin >> s1 >> s2;
int t[6] = { 0,1,3,4,6,7 };
for (int i = 0;i < 6;++i) {
s3[i] = s2[t[i]];
s4[i] = s1[t[i]] - '0';
}
for (int i = 0;i < 6;++i) {
int x = s4[i];
if (s3[i] == 'B') ban[x] = 1;
else ++num[x];
}
dfs(0);
cout << ansnum << endl;
for (int i = 1;i <= ansnum;++i) {
cout << ans[i][0] << ans[i][1] << "+";
cout << ans[i][2] << ans[i][3] << "=";
cout << ans[i][4] << ans[i][5] << endl;
}
return 0;
}
E Expenditure Reduction
Description
给定一个字符串和该字符串的一个子序列。
求该字符串的最短的包含该子序列的子串。
Solution
易得答案子串的开头与结尾字符等于给定子序列的开头和结尾字符。
我们要做的就是找到结尾和开头位置相差最小的子序列。
法一:
在考场里想到的是:记录一个二维数组\(pos_{i,j}\)表示从左往右第\(j\)字符\(i\)的位置,然后对每一个\(F_0\)字符往后找到一个跨度最小的子序列。从一个字符找到下一个字符的方法是:找到下一个字符的比当前位置大的最小位置(二分)。
其实可以优化一下:记录一个数组\(nxt_{i,j}\)表示位置\(i\)往后的第一个\(j\)字符的位置。然后对每一个\(F_0\)字符往后找,直到得到整个子序列,并且更新答案。复杂度\(O(|S|*|\alpha|+|S|*|F|)\)。
注意的细节: 更新答案不要复制字符串(会TLE)!只要记录起始地址和答案字符串长度即可。
法二:
动态规划。
\(f_{i,j}\)表示考虑到\(S\)的第\(i\)个位置,\(F\)匹配到第\(j\)个位置的字符串长度。
答案:\(min(f_{i,|F|})\)
具体地说:
(为了转移方便,给 \(i,j\) 统一全部加上\(1\))
-
初始条件: \(\forall i\in [0,|S|],j=0\ f[i][j]=0\)
\(\forall i\in[0,|S|],\forall j\in[1,|F|]\ f[i][j]=inf\)
-
转移: \(f[i][j]=min\{f[i][j],f[i-1][j]+1\}\)
若\(S[i-1]==F[j-1],f[i][j]=min\{f[i][j],f[i-1][j-1]+1\}\)
-
答案: 找到最小的\(f[i][|F|]\),答案字符串为\(S[i-f[i][|F|+1]...S[i]\)。
Code1
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 5, M = 105;;
int nxt[N][40]; //注意组成字符串的字符有 '0'~'9' 和 'a'~'z' 两个类型
char S[N], F[M];
char get(int x) {
if (x < 26) return 'a' + x;
return x - 26 + '0';
}
int ind(char c) {
if (c >= 'a' && c <= 'z') return c - 'a';
return c - '0' + 26;
}
int main() {
int T;
cin >> T;
while (T--) {
cin >> S >> F;
int lens = strlen(S), lenf = strlen(F), anslen = lens, ansbeg = 0;
//首先初始化nxt数组
for (int i = 0;i < 36;++i) {
char c = get(i);
int nxtpos = 0;
for (int j = lens - 1;j >= 0;--j) {
nxt[j][i] = nxtpos;
if (S[j] == c) nxtpos = j;
}
}
//从S的每个位置开始尝试寻找F
for (int i = 0;i < lens;++i) {
if (S[i] != F[0])continue;
int curpos = i;
bool flag = 1;
for (int j = 1;j < lenf;++j) {
if (nxt[curpos][ind(F[j])] == 0) {
flag = 0;
break;
}
curpos = nxt[curpos][ind(F[j])];
}
if (!flag)continue;
int curlen = curpos - i + 1;
if (curlen >= anslen)continue;
//更新答案
anslen = curlen;
ansbeg = i;
}
for (int i = ansbeg;i < ansbeg + anslen;++i) cout << S[i];
cout << endl;
for (int i = 0;i < lens;++i)
for (int j = 0;j < 36;++j)
nxt[i][j] = 0;
}
return 0;
}
Code2
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1e5 + 5, M = 105;;
int f[N][M];
char S[N], F[M];
int main() {
int T;
cin >> T;
while (T--) {
cin >> S >> F;
int lens = strlen(S), lenf = strlen(F);
//初始化
for (int i = 0;i <= lens;++i)
for (int j = 1;j <= lenf;++j)
f[i][j] = 1e9;
//dp
for (int i = 1;i <= lens;++i) {
for (int j = 1;j <= lenf;++j) {
f[i][j] = min(f[i][j], f[i - 1][j] + 1);
if (S[i - 1] == F[j - 1])
f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
}
//统计并输出答案
int anslen = lens, ansend = lens-1;
for (int i = 1;i <= lens;++i)
if (anslen > f[i][lenf]) {
anslen = f[i][lenf];
ansend = i - 1;
}
for (int i = ansend - anslen + 1;i <= ansend;++i)
printf("%c", S[i]);
printf("\n");
}
return 0;
}
G Gua!
Sol
判断S * (R / 60.0) * B 与 D 的大小关系即可。
H Heirloom Painting
Descirption
一个圆环分为 \(n\) 个格子,共有 \(m\) 个颜色。给格子涂颜色,一次必须涂连续的 \(k\) 个格子,后涂的颜色可以覆盖先涂的颜色。所有格子初始无色,给定所有格子的预期颜色,问最少涂多少次能使每个格子涂成预期颜色。
Solution
注意到最后一次涂颜色一定会得到连续 \(k\) 同样颜色的格子。由此可以得到判断能否完成任务的条件:若答案圆环没有连续 \(k\) 个一样颜色的格子,则无法完成任务;否则一定可以完成任务。
从任意两个相邻的不同色的格子中间将环断开成链,扫描整条链,对于每一个长度为 \(l\) 的相同颜色区间,涂色次数为\(\lceil len/l\rceil\),累加所有区间涂色次数,\(\sum \lceil len/l\rceil\) 即为最终答案。
Code
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
const int N = 1e6 + 5;
int n, m, k, a[N << 1], ans;
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d%d", &n, &m, &k);
for (int i = 1;i <= n;++i) {
scanf("%d", &a[i]);
a[i + n] = a[i];
}
int len = 1, maxlen = 1, pos = 1;
for (int i = 2;i <= n * 2;++i) {
if (a[i] == a[i - 1]) ++len;
else {
len = 1;
if (i <= n) pos = i;
}
if (len > maxlen) maxlen = len;
}
if (len > maxlen) maxlen = len;
if (maxlen < k) {
printf("-1\n");
continue;
}
ans = 0;
len = 1;
for (int i = pos + 1;i < pos + n;++i) {
if (a[i] == a[i - 1]) ++len;
else ans += (len + k - 1) / k, len = 1;
}
ans += (len + k - 1) / k;
printf("%d\n", ans);
}
return 0;
}
M My University Is Better Than Yours
Description
有 \(n\) 个学校,\(m\) 个排行榜。定义一个学校 \(x\) 好于 \(y\),当且仅当存在一个排行榜,\(x\) 排在 \(y\) 前面。
对于每一所学校,问这所学校好于多少所学校。
Solution
对于每一个排行榜,\(\forall i\in[1,n-1]\),建从 \(a_i\) 到 \(a_{i+1}\) 的边。最后得到一个有向图。
对于每一个结点,答案为以它为起点能到达的点的数目。
\(Tarjan\) 缩点得到 \(DAG\) 图。考虑本题的特殊性质,对于任意两个学校,一定有路能从其中某个通向另外一个。所以最后得到的是一条链(注意要进行一轮拓扑排序得到),可以直接统计答案。
N Nine Is Greater Than Ten
签到题,直接比较字符串大小即可。