[ UVa 12096 详解] The Set Stack Computer 集合栈计算机 | map、set、vector、stack、宏函数等知识点简单应用

题目

有一个专门为了集合运算而设计的“集合栈”计算机。该机器有一个初始为空的栈,并且支持以下操作:

  • PUSH:空集“{}”入栈

  • DUP:把当前栈顶元素复制一份后再入栈

  • UNION:出栈两个集合,然后把两者的并集入栈

  • INTERSECT:出栈两个集合,然后把二者的交集入栈

  • ADD:出栈两个集合,然后把先出栈的集合加入到后出栈的集合中,把结果入栈

每次操作后,输出栈顶集合的大小(即元素个数)。

例如栈顶元素是A={ {}, {{}} }, 下一个元素是B={ {}, {{{}}} },

则:

UNION 操作将得到{ {}, {{}}, {{{}}} },输出 3

INTERSECT 操作将得到{ {} },输出 1

ADD 操作将得到{ {}, {{{}}}, { {}, {{}} } },输出 3

Input:

6
PUSH
PUSH
UNION
PUSH
PUSH
ADD

Output:

0
0
0
0
0
1

分析

题意理解:

这集合嵌套属实脑壳痛,现在可以确定会用到 stack 和 set 两种数据类型

PUSH 只需要在栈顶加个空集合,DUP也很好实现

UNION 和 INTERSECT 取交并集可以使用 algorithm 库来实现

ADD 则可以使用 集合自带的 .insert

但是,set 指明类型时并不能 指set,也就是说 set<set> Set 这条命令是无效的

直接导致 ADD 操作无法实现,所以我们需要重新规划一下数据类型

数据类型:

经过取舍,int类的集合是比较好,我们可以用 数字 来指代每一个独立不重复的集合

例如 {} -- 0 ;

{{},{}} -- 用{0,0}表示 -- 2

{{{},{}} , {{},{}} } -- 用{2 ,2}表示 -- 3

(当然 stack 也是 int类)

以此类推,每得到一个新的集合就用新的数字标识,然后组成以后出现的 集合

那么 vector 就是很好的容器,

vector<Set> Setcache; // Set 是通过 typedef set<int> Set 定义,减少代码量,不必每次都写  set<int>

每一个新的集合就用 加入后的下标 标识 Setcache.size() - 1

从数字找到对应集合解决了,还有从集合找到对应的数字(至于为什么,想想PUSH如何实现就好)

这种映射关系显然可以使用 map

map<Set,int> IDcache; 

至于栈中的集合,就用数字来表示

那么数据类型大概就是

map, vector, set, stack 这四种

typedef set<int> Set;
map<Set,int> IDcache;
vector<Set> Setcache;
stack<int> ans;

操作实现:

  • PUSH :

使用 Set() 来生成一个空集合,ans.push() 来加入栈顶,

因为加入的是空集合对应的数字,我们自定义一个函数来实现 集合与数字的转换

ans.push( ID( Set() ) ) // ID() 即为转换函数,后文会介绍
  • DUP:

ans.top() 来获得当前栈顶的元素,再用 ans.push() 加入进去即可

ans.push(ans.top())
  • UNION 和 INTERSECT :

使用 algorithm 库里的 set_unionset_intersection 来实现

这是它的参数(两个相同)

set_union(
    A.begin(),
    A.end(),		// 第一个集合的开始与结尾
    B.begin(),
    B.end(),		// 第二个集合的开始与结尾
    inserter( C , C.begin() ) // 意思是将集合 A、B 取合集后的结果存入集合 C 中
);

如果都要全写上实在有点繁琐,而且两个的参数都类似

我们可以使用 宏函数 来使它简化

// 宏函数在预编译时,用函数定义的代码段来替换函数名,将函数代码段嵌入到当前程序,不会产生函数调用
// 可以理解为一段代码的代替物,但相对于函数效率要高(代码量小的情况下)
#define ALL(x) x.begin(),x.end()
#define ITE(x) inserter(x, x.begin())

所以可这么写

set_union(ALL(x1),ALL(x2),ITE(x));
set_intersection(ALL(x1), ALL(x2), ITE(x));
  • ADD:

x.insert() 来加入 集合对应的数字 即可

x = x2;
x.insert( ID(x1) )

因为后三个操作都需要提取两个集合,可以分为一组处理,最后加上 push 操作

  • ID():

传入的是集合类型,所以参数设为 Set set 返回数字

int ID(Set set)
{
	if (IDcache.count(set)) return IDcache[set]; // 若已经生成过对应的数字则直接返回
	Setcache.push_back(set);					// 将集合插入 vector 容器中
	return IDcache[set] = Setcache.size() - 1;  // 计算当前下标赋值给 map容器 并返回
}

完全体

#include <iostream>	// 经典输入输出
#include <set>
#include <vector>
#include <map>
#include <stack>
#include <string>	// 取指令需要
#include <algorithm>
#include <iterator> // inserter 需要

#define ALL(x)  x.begin(),x.end()
#define ITE(x)  inserter(x,x.begin())

using namespace std;

typedef set<int> Set;
map<Set, int> IDcache;
vector<Set> Setcache;

stack<int> ans;

int ID(Set set);


int main()
{
	int n = 0;
	cin >> n;
	string cmd;
	for (int i = 0; i < n; i++)
	{
		cin >> cmd;
		if (cmd[0] == 'P') ans.push( ID( Set() ) );
		else if (cmd[0] == 'D') ans.push(ans.top());
		else {
			Set x1 = Setcache[ans.top()]; ans.pop();
			Set x2 = Setcache[ans.top()]; ans.pop();
			Set x;
			if (cmd[0] == 'U') set_union(ALL(x1), ALL(x2), ITE(x));
			if (cmd[0] == 'I') set_intersection(ALL(x1), ALL(x2), ITE(x));
			if (cmd[0] == 'A')
			{
				x = x2;
				x.insert(ID(x1));
			}
			ans.push(ID(x));
		}
		cout << Setcache[ans.top()].size() << endl;
	}
}


int ID(Set set)
{
	if (IDcache.count(set)) return IDcache[set];
	Setcache.push_back(set);
	return IDcache[set] = Setcache.size() - 1;
}

技术性总结

相比上次的 vector 应用,这回操作实现并不复杂,数据类型的组织反而复杂起来了

使用了 map set stack 这几个新的容器,收获丰富

还有宏定义的手法,又能摸鱼了(?)

用时一个下午,做题 + 写总结

扩展阅读 / 参考源

原文地址:https://www.cnblogs.com/edwinaze/p/15248434.html