有一个整数集合,初始时集合为空。
现在,要对该集合进行 tt 次操作,操作分为以下三种:
+ x,将一个非负整数 xx 添加至集合中。注意,集合中可以存在多个相同的整数。- x,从集合中删除一个非负整数 xx。可以保证执行此操作时,集合中至少存在一个 xx。? s,询问操作,给定一个由 00 和 11 组成的模板 ss,请你计算并输出此时集合中有多少个元素可以与 ss 相匹配。关于判断整数 xx 与模板 ss 是否匹配的具体方法如下:
例如,如果 s=010s=010,则 92、2212、50、41492、2212、50、414 与 ss 匹配,而 3、110、25、10303、110、25、1030 与 ss 不匹配。
输入格式
第一行包含整数 tt,表示操作次数。
接下来 tt 行,每行包含一个操作,格式如题面描述。
保证至少存在一个查询操作。
输出格式
每个查询操作输出一行结果,一个整数,表示集合中与 ss 匹配的元素个数。
数据范围
前 44 个测试点满足 1≤t≤201≤t≤20。
所有测试点满足 1≤t≤1051≤t≤105,0≤x<10180≤x<1018,ss 的长度范围 [1,18][1,18]。
输入样例1:
- 12
- + 1
- + 241
- ? 1
- + 361
- - 241
- ? 0101
- + 101
- ? 101
- - 101
- ? 101
- + 4000
- ? 0
输出样例1:
- 2
- 1
- 2
- 1
- 1
输入样例2:
- 5
- + 200
- + 200
- ? 0
- - 200
- ? 0
输出样例2:
- 2
- 1
思路: 所有的数最大18位,所以可以分为2^18种可能(每一位数都是偶数或者奇数,最长18位)。
可以用字典树写,也可以用一种神奇的写法。
字典树写法:
- #include<iostream>
-
- using namespace std;
- typedef long long LL;
- const int N = 3e6 + 10;
- int q[N];
- void add(LL x)
- {
- LL res=1;
- for(int i=1;i<=18;i++)
- {
- int y=x%10;
- x/=10;
- if(y%2) res=res*2+1;
- else res*=2;
- }
- q[res]++;
- }
- void sub(LL x)
- {
- LL res=1;
- for(int i=1;i<=18;i++)
- {
- int y=x%10;
- x/=10;
- if(y%2) res=res*2+1;
- else res*=2;
- }
- q[res]--;
- }
- int query(LL x)
- {
- int res=1;
- for(int i=1;i<=18;i++)
- {
- int y=x%10;
- x/=10;
- if(y%2) res=res*2+1;
- else res*=2;
- }
- return q[res];
- }
- int main()
- {
- int n;
- cin>>n;
- char op[2];
- LL x;
- while(n--)
- {
- scanf("%s%lld",op,&x);
-
- if(*op=='+') add(x);
- else if(*op=='-') sub(x);
- else printf("%d\n",query(x));
- }
- return 0;
- }
神奇的写法:
- #include<iostream>
-
- using namespace std;
- const int N = 3e5 + 10;
- int q[N];
-
- int main()
- {
- int n;
- cin>>n;
- char op[2],str[20];
- while(n--)
- {
- scanf("%s%s",op,str);
- int x=0;
- for(int i=0;str[i];i++)
- {
- x=x*2+str[i]%2;//相当于字典树写法
- //这句话就是将一个数字按照各位置的奇偶性转化为对应的二进制数
- //str[i]没有str[i] - '0'的原因是字符'0'、'1'、'2'、'3'...
- //对应的ASCII码是48、49、50、51…48、49、50、51…,
- //奇偶性相同,可以直接计算。
- }
- if(*op=='+') q[x]++;
- else if(*op=='-') q[x]--;
- else printf("%d\n",q[x]);
- }
- return 0;
- }