题解:CF878C Tournament
题目传送门
题意
有 \(n\) 个人和 \(k\) 个项目,要求每次比赛会随机选两个人,在一个随机项目上比拼,在该项目上能力值较小的人会被淘汰,直到剩下一个人,那么他就是冠军。现在要求求出只有前 \(i\) 个人时,冠军的可能数量。
思路
容易发现,当一个人每一项项目的能力值都大于另一个人时,那么这个人就是绝对碾压另一个人,如果这有这连个个人的话,那冠军只有可能是一个。但是当不是绝对碾压的情况的话,两个人都有可能成为冠军。
那么根据这个绝对碾压的情况,我们考虑已经构建出了一个冠军集合,即里面所有人都有可能成为冠军。那么当加入一个新人时,我们尝试这个新人对其他人是否有绝对碾压,如果是的话,就不合并,否则就合并。
那么怎么合并呢?我们想到可以用 set 来维护这个集合。对于一个集合,我们只需要维护它的 size 和每个项目的最大能力值和最小能力值。之后就可以通过重载运算符来判断是否有完全碾压,如果这个集合的每一个项目的最大能力值都要小于另一个集合的每一个项目的最小能力值,那么就是完全碾压。
这里贴出重载的代码。
struct node{
ll ans;
ll maxn[12],minn[12];
node(){
memset(maxn,0,sizeof(maxn));
memset(minn,0x3f,sizeof(minn));
}
friend bool operator <(node a,node b){
for(int i=1;i<=k;i++)if(a.maxn[i]>b.minn[i])return false;
return true;
}
};
之后要算答案就很简单,因为我们重载的是小于号,所以 set 中的最后一个集合就是我们需要的冠军集合,输出它的 size 即可。
要注意一点,我在代码中用来判断是否绝对碾压时用的是 set 自带的 find 函数,因为重载了小于号,所以这个函数判断等于的方式就变成了只要这两个集合不是绝对碾压关系,那么他们就是相等的。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1010101;
ll n,k;
struct node{
ll ans;
ll maxn[12],minn[12];
node(){
memset(maxn,0,sizeof(maxn));
memset(minn,0x3f,sizeof(minn));
}
friend bool operator <(node a,node b){
for(int i=1;i<=k;i++)if(a.maxn[i]>b.minn[i])return false;
return true;
}
};
set<node>st;
set<node>::iterator it;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>k;
node res;
for(int i=1;i<=n;i++){
res.ans=1;
for(int j=1;j<=k;j++){
ll x;
cin>>x;
res.maxn[j]=res.minn[j]=x;
}
while(st.find(res)!=st.end()){
it=st.find(res);
res.ans+=it->ans;
for(int j=1;j<=k;j++){
res.maxn[j]=max(res.maxn[j],it->maxn[j]);
res.minn[j]=min(res.minn[j],it->minn[j]);
}
st.erase(it);
}
st.insert(res);
cout<<(--st.end())->ans<<"\n";
}
}

浙公网安备 33010602011771号