[Codeforces 878C] Tournament

题目链接: http://codeforces.com/problemset/problem/878/C

题目大意: 有一个锦标赛, 一共有n名运动员参加,k种运动项目。 第i个运动员的第k个项目的能力值记为 si,j 。 锦标赛的过程均为不确定的, 即每次任选两名未被淘汰的运动员比拼某一个项目, 赢得人留下, 输的人淘汰, 最终留下的人即为冠军。 锦标赛第一年只有一个人参加, 往后每年多一个人直到第n年。 求每一年可能有多少种不同的冠军产生。 (n5104,k10,si,j109) , 保证每一个项目中的每一个人的能力值不同。

思路: 考虑转换模型。 如果每一个运动员看成一个点, 如果i有可能打败j则连一条i->j的有向边。 则答案即为强连通分量缩点后, 入度为0的那个强连通分量所包含的点的个数。

考虑这个图的特征, 每两个点之间一定至少有一条边。 作强连通分量缩点后, 两个点之间仅有一条有向边, 即为该点的所有运动项目都是“完全碾压”另一个点的。 我们可以将“完全碾压”这个关系重载小于号, 将强连通分量们按此排序。 每个强连通分量维护每项运动的最小值与最大值和所包含的点的个数。

考虑新加入一个点, 最后一个大于它的强连通分量l, 与第一个小于它的强连通分量r, 则该点将与[l + 1, r - 1]中的所有点合并成一个新的强连通分量并重新插入序列中。 该过程可以用set来实现。

#include <set>
#include <queue>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int inf = 1 << 30;

int n, k;
struct Node{
    int mx[20], mn[20], sz;
    void init(int _, int __){
        for (int i = 1; i <= k; i ++)
            mx[i] = mn[i] = _;
        sz = __;
    }
    bool operator<(const Node &_) const{
        for (int i = 1; i <= k; i ++){
            if (mx[i] > _.mn[i]) return 0;
        }
        return 1;
    }
    void unit(const Node &_){
        sz += _.sz;
        for (int i = 1; i <= k; i ++)
            mx[i] = max(mx[i], _.mx[i]), mn[i] = min(mn[i], _.mn[i]);
    }
};

set<Node > S;
vector< set<Node > :: iterator  > del;

int main(){

    scanf("%d%d", &n, &k);

    Node tmp;
    tmp.init(0, 0); S.insert(tmp);
    tmp.init(inf, 0); S.insert(tmp);

    for (int i = 1; i <= n; i ++){
        tmp.sz = 1;
        for (int j = 1; j <= k; j ++)
            scanf("%d", tmp.mx + j), tmp.mn[j] = tmp.mx[j];

        set<Node > :: iterator  l, r, it;
        l = S.lower_bound(tmp);
        r = S.upper_bound(tmp);

        for (it = l; it != r; it ++){
            del.push_back(it);
            tmp.unit(*it);
        }

        for (int j = 0; j < del.size(); j ++) S.erase(del[j]);

        del.clear();


        S.insert(tmp);
        it = S.end(); it --; it --;

        printf("%d\n", it -> sz);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值