对于UVa12096“集合的集合”的理解

这里为每个集合分配了一个唯一的 ID,使用了 std::map 实现集合到整数类型的对应。map 中的集合属于对象类型,起初难以接受,不过既然是模板,那么什么类型都是可以装载的。
函数 ID 起到了从 Set 到 int 的转换,并且添加了新家入的集合和整数ID的映射以及ID到集合的存取

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

乍一看代码有点不知所措,其实分析明白容器中都装的是什么就会明白:
Set 实例化的对象如果是空集那么其中就没有元素,如果有其他集合,呢么 set 中的元素就是代表集合的 ID 整数。而每个集合又都有 map<Set, int> 为之分配的 ID 。而 stack 中装载就就是这样的 ID。实质就是模拟操作,只不过将 Set 映射 到了 int。

整个的逻辑是这样的:
判断是何操作:
1.PUSH:向栈 s 中 push 一个经过 ID 函数转换后集合对应的整数
2.DUP:再向集合中加入一个栈顶层的整数元素
3.UNION:取出两栈顶元素,经set_union后为这个新集合分配ID,再push
4.INTERSECT:取出两栈顶元素,经set_intersection后卫这个新集合分配ID,再push

其AC代码如下:

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

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

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


int main() {
    std::ios::sync_with_stdio(false);

    int T; cin >> T;
    while (T--) {
        stack<int> s;
        int n; cin >> n;
        for (int i = 0; i < n; ++i) {
            string op;
            cin >> op;
            if (op[0] == 'P') s.push(ID(Set()));
            else if (op[0] == 'D') s.push(s.top());
            else {
                Set x1 = Setcache[s.top()]; s.pop();
                Set x2 = Setcache[s.top()]; s.pop();
                Set x;
                if (op[0] == 'U') set_union(ALL(x1), ALL(x2), INS(x));
                if (op[0] == 'I') set_intersection(ALL(x1), ALL(x2), INS(x));
                if (op[0] == 'A') { x = x2; x.insert(ID(x1)); }
                s.push(ID(x));
            }
            cout << Setcache[s.top()].size() << endl;
        }
        cout << "***" << endl;
    }
    return 0;
}

以及UVa1592 http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=51293
也使用了类似的将一个对象映射成一个整型的方法/思路

using namespace std;

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

const int maxr = 10000 + 5;
const int maxc = 10 + 5;
typedef pair<int, int> PII;

int m, n, db[maxr][maxc], cnt;

map<string, int> id;    // string --> ID
int ID(const string &s) {
    if (!id.count(s)) id[s] = ++cnt;
    return id[s];
}

void find() {
    for (int c1 = 0; c1 < m; ++c1) {
        for (int c2 = c1 + 1; c2 < m; ++c2) {
            map<PII, int> d;
            for (int i = 0; i < n; ++i) {
                PII p = make_pair(db[i][c1], db[i][c2]);
                if (d.count(p)) {
                    printf("NO\n");
                    printf("%d %d\n", d[p] + 1, i + 1);
                    printf("%d %d\n", c1 + 1, c2 + 1);
                    return;
                }
                d[p] = i;
            }
        }
    }
    printf("YES\n");
}


int main() {

#ifdef LOCAL
    freopen("input.txt", "r", stdin);
    freopen("output.txt", "w", stdout);
#endif

    std::ios::sync_with_stdio(false);

    string s;
    while (getline(cin, s)) {
        // construct a stringstream from a string s
        stringstream ss(s);
        if (!(ss >> n >> m)) break;
        cnt = 0;
        id.clear();
        // 将输入的字符串映射为 数字,在 db 中存储 int
        for (int i = 0; i < n; ++i) {
            getline(cin, s);
            // 从第 lastpos 处开始线性查找字符
            int lastpos = -1;
            for (int j = 0; j < m; ++j) {
                int p = s.find(',', lastpos + 1);
                if (p == string::npos) p = s.length();
                // lastpos 为字符串起始位置,p - lastpos - 1 为字符串长度
                db[i][j] = ID(s.substr(lastpos + 1, p - lastpos - 1));
                lastpos = p;
            }
        }
        find();
    }
    return 0;
}

其接收两个整型参数的操作有些诡异,是为了演示 stringstream 的用法。可以使用 while(cin >> m >> n && m) 的写法。

    while (getline(cin, s)) {
        // construct a stringstream from a string s
        stringstream ss(s);
        if (!(ss >> n >> m)) break;

这个映射函数的写法同之前的 set –> int 一样的写法,若此对象不存在(!id.count(s))则添加到 map 中,无论存在与否,返回这个 ID。

int ID(const string &s) {
    if (!id.count(s)) id[s] = ++cnt;
    return id[s];
}

使用 getline(cin, s) 循环接收 n 行字符串,在处理每行字符串前定义一个 lastpos 用以记录字符串处理到哪里。接着对 m 列字符串处理,查找 ‘,’ 得到查找到的位置 p,判断是否找到使用到了 string::pos,如果 p==string::pos 说明没有找到。substr 将截取子字符串并由 ID 函数转化为一个 ID ,最终存储在 db 数组 中。

for (int i = 0; i < n; ++i) {
    getline(cin, s);
    // 从第 lastpos 处开始线性查找字符
    int lastpos = -1;
    for (int j = 0; j < m; ++j) {
        int p = s.find(',', lastpos + 1);
        if (p == string::npos) p = s.length();
        // lastpos 为字符串起始位置,p - lastpos - 1 为字符串长度
        db[i][j] = ID(s.substr(lastpos + 1, p - lastpos - 1));
        // 再从找到 ',' 的地方开始查找
        lastpos = p;
    }
}

最后是 find 函数,枚举所有的两列。并对每个两列进行枚举每行,若出现了相同的对,则说明有重复。这里的 map

void find() {
    for (int c1 = 0; c1 < m; ++c1) {
        for (int c2 = c1 + 1; c2 < m; ++c2) {
            map<PII, int> d;
            for (int i = 0; i < n; ++i) {
                PII p = make_pair(db[i][c1], db[i][c2]);
                if (d.count(p)) {
                    printf("NO\n");
                    printf("%d %d\n", d[p] + 1, i + 1);
                    printf("%d %d\n", c1 + 1, c2 + 1);
                    return;
                }
                d[p] = i;
            }
        }
    }
    printf("YES\n");
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值