题目描述很简单,难点在于如何对集合进行编码,因为是无限的,好像没有一个方向进行编码。
紫书给的题解十分巧妙:给新出现的集合进行编码
的确,我们没有必要为所有可能出现的集合编码后再开始,我们就可以简单的根据出现的次序分配一个映射值即可,这个值只要能够代表这个集合并且不发生碰撞。
另一个巧妙的点是STL中的map
竟然支持从对set
的哈希,这个也太神奇了,虽然不明白是怎么做的,可能要看源码才能理解。
代码如下:
需要注意的一点是在switch
语句中的case
语句后面不能直接声明局部变量,要放在大括号里面,形成一个局部变量。其原因是如果直接定义的话,其他case
语句也是可以看到这个变量的,但是如果不执行那个定义的case
语句,就会导致变量声明却没有定义。原因在于switch
其实就是一种奇特的goto
。
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <stack>
#include <string>
#include <algorithm>
#include <iterator>
using namespace std;
using Set = set<int>;
class SetHash {
vector<Set> num2set; //保存num到set的映射
map<Set, int> set2num; //保存set到num的映射
public:
int operator ()(Set s); //获取一个set的hash值
Set operator ()(int num);//获取一个hash值为num的set
int getSize(int num) const;
};
int SetHash::operator()(Set s) {
if (!set2num.count(s)) {
set2num[s] = num2set.size();
num2set.push_back(s);
return num2set.size() - 1;
} else {
return set2num[s];
}
}
Set SetHash::operator()(int num) {
return num2set[num];
}
int SetHash::getSize(int num) const {
return num2set[num].size();
}
stack<int> stk; //用于保存集合栈
int main() {
ios::sync_with_stdio(false);
int T, n;
string cmd;
SetHash setHash;
cin >> T;
while (T--) {
cin >> n;
while (n--) {
cin >> cmd;
if (cmd[0] == 'P') stk.push(setHash(Set()));
else if (cmd[0] == 'D') stk.push(stk.top());
else {
Set a = setHash(stk.top()); stk.pop();
Set b = setHash(stk.top()); stk.pop();
switch (cmd[0]) {
case 'U':
b.insert(a.begin(), a.end());
stk.push(setHash(b));
break;
case 'I':
{
Set c;
set_intersection(a.begin(), a.end(), b.begin(), b.end(), inserter(c, c.begin()));
stk.push(setHash(c));
break;
}
case 'A':
b.insert(setHash(a));
stk.push(setHash(b));
break;
}
//
}
cout << setHash.getSize(stk.top()) << "\n";
}
cout << "***\n";
}
}