给你一个字符串,里面最多有一个'?','?'可以表示'a' - 'z',也可以什么都不表
示,这里要明确,什么都不表示不是不存在的意思,当aa什么都不表示的时候aa 也不等于aa? ,因为这个wa的快吐血了,题目要求输出一共有多少个子串满足所有字母('a' -'z')出现的次数必须是偶数个。
思路:
首先对于每一个前缀串的奇偶状态我们可以用mark[1<<26]的状态压缩表示,如果当前字母的个数是偶数,那么对应位置的状态就是0,奇数是1,先不考虑'?',假如当前的这个状态在之前出现过,那么当前可以增加的子串数量就是当前状态之前出现的次数,为什么是这样呢?两个相同的状态之间肯定是经过了偶数次变化,奇数 + 偶数 = 奇数,偶数 + 偶数 = 偶数,所以说前面有多少个当前状态,就有多少个偶数间隔,就会有几个满足题意的子串,(也可以全求出来,然后排列组合C(m,2))一个意思,现在在把'?'考虑进来,先算出没意义的情况,然后在枚举他是'a' - 'z',枚举的时候我是先枚举一遍以'?'结尾的,然后对于?后面的直接就把每一个状态的每一位都改变一次,求出所有的和就行了,还有要注意的一点就是mark[1<<26]数组很大,memset清不起,我们要采取每次只把用过的清空的原则去减少时间。感觉没说清楚,但这个题目不是很好说明白,自己做了将近一天才AC,要是看不懂就看看代码然后自己在想想吧。
#include<stdio.h> #include<string.h> int sum[(1<<26)+10]; int num[22000]; char str[22000]; int main () { int i ,j ,t ,ans; scanf("%d" ,&t); while(t--) { scanf("%s" ,str); int len = strlen(str); int mki = -1 ,tt = 0; num[0] = 0; sum[num[0]] = 1; for(ans = i = 0 ;i < len ;i ++) { if(str[i] == '?') { mki = i; ans += sum[num[tt]]; sum[num[tt]] ++; continue; } tt ++; num[tt] = num[tt-1] ^ (1 << (str[i] - 'a')); ans += sum[num[tt]]; sum[num[tt]] ++; } for(i = 0 ;i <= tt ;i ++) sum[num[i]] = 0; if(mki == -1) { printf("%d " ,ans); continue; } tt = 0; num[0] = 0; sum[num[0]] = 1; for(i = 0 ;i < mki ;i ++) { tt ++; num[tt] = num[tt-1] ^ (1 << (str[i] - 'a')); sum[num[tt]] ++; } for(i = 'a' ;i <= 'z' ;i ++) { int tmp = num[tt] ^ (1 << (i - 'a')); ans += sum[tmp]; } for(i = mki + 1 ;i < len ;i ++) { tt ++; num[tt] = num[tt-1] ^ (1 << (str[i] - 'a')); for(j = 0 ;j < 26 ;j ++) ans += sum[num[tt]^(1<<j)]; } for(int i = 0 ;i <= tt ;i ++) sum[num[i]] = 0; printf("%d " ,ans); } return 0; }