题目描述
题目释义
字符串s的每一个字符颜色都是红绿白之一
规则:
- 颜色相同的字符均两两不相等
- 红绿颜色字符数量相同
- 能涂的都要涂
算法设计
如果暴力求解,则遍历所有情况为3的50次方,远远超时
观察得到涂的什么颜色并不影响判断,只需知道字符有没有被涂色。规则等价于
- 涂色的数量为偶数
- 涂色的字符中相同的字符不超过2个
若同时满足以上两个规则,则必然可以找出至少一种满足题设要求的涂色方案。
因为只需要:
- 将成对出现的字符一个涂成红色,一个涂成绿色
- 剩下的单个出现的字符总数必然是个不相等且共有偶数个,那么一半涂红一半涂绿
这样就得到了一个wonderful coloring
例:1表示红2表示绿
kzaaa,知道了11101
那就一个a涂红一个a涂绿,kz一个涂红一个涂绿
所以kzaaa 21102、12201等等都是wonderful coloring
所以只需一个数组标志每个字符涂没涂,判断
- 有偶数个1
- 同一个字符最多出现2次
由于要求的是被涂元素个数最多的情况,先考虑有小于s.length的最大偶数n个元素被涂色,若没找到最佳方案,n一次-2即可。
如何遍历?
问题相当于遍历从a1,a2,……an中选取k个元素的所有情况,选k个就需要k层循环,这基本上不可能简单的实现
进一步思考,只需要:
- 选取偶数个成单元素涂色
- 选取任意数量的成对元素
例如pakawdac,只需要列出一个表记录每个字符出现的个数:
p,k,w,d,c各出现1次,a出现3次
则只需要从pkwdc五个中选4个涂色,选一对aa涂色即可。结果就是有三个涂成了红色。
记ou(a)表示不大于a的最大偶数
所以只需要记录:
n元素出现了1次,k个元素出现了多次
最终结果就是ou(n)/2+k
更进一步,只需要记录出现超过两次的字符,出现单次的字符用字符串长度减就可以了。
**算法关键: **
数据处理
每一组输入存入s[50],分别进行运算,结果储存在ans[T]中
int T;
cin >> T;
int ans[T];
int l = 0;
for (int i = 0;s i < T; ++i) {
char s[50];
cin >> s;
ans[l++] = solve(s);
}
运算逻辑
函数solve:输入字符串
- num为单次出现字符数量,res为多次出现字符数量,ans为答案
- 因为数组删除元素困难,定义repeat专门存放已经计入结果的字母,当i指向下一个字符后判断该字符是否是repeat中的元素,如果是直接goto开启下一个循环即可
- flag表示该字符出现过1次还是多次,以便对res或num进行增加。
- 核心:但凡找到一个s[j]=temp,直接break并将该字母记为重复。
int solve(const char *s) {
int ans;
int res = 0;
int num = 0;
char repeat[100];
int reLen = 0;
int flag = 1;//0多次出现1多出现一次
for (int i = 0; s[i]; ++i) {
char temp = s[i];
for (int j = 0; j < reLen; ++j) {
if (repeat[j] == temp) {
goto end;
}
}
for (int k = i + 1; s[k]; ++k) {
if (s[k] == temp) {
flag = 0;
res++;
repeat[reLen++] = temp;
break;
}
}
if (flag) {
num++;
}
flag = 1;
end:;
}
if (num % 2) { num--; }
ans = res + num/2;
return ans;
}
输出处理
for (int i = 0; i < l; ++i) {
cout << ans[i] << endl;
}
总结
- 对于多组输入分开处理的input,建立答案数组储存每一个的答案。
- 通过对题意的分析得出了高效的算法。心路历程始终是:将逻辑陈述简化并逐步转变为可操作型语言。