原题:小Hi的烦恼
原题大意
描述
小Hi从小的一大兴趣爱好就是学习,但是他发现尽管他认真学习,依旧有学神考的比他好。
小Hi在高中期间参加了市里的期末考试,一共五门:语文、数学、英语、物理、化学。
成绩出来之后,小Hi发现有些同学,所有科目都考的比他好,他很烦恼。所以他想知道所有科目都比自己名次靠前的同学的人数。
为了方便,可以认为不存在两个人某一门名次是相同的。
其他同学们也想知道有多少人全面碾压了他们,所以你需要对所有人输出答案。
输入
第一行,一个正整数N(N <= 30000),表示人数。
接下来N行,每行五个整数,分别表示五门课依次的排名。
输出
输出共N行,每行一个整数,表示答案。
样例输入
4
1 1 2 2 1
2 3 3 3 2
3 2 1 1 3
4 4 4 4 4
样例输出
0
1
0
3
算法分析
这一题主要是bitset的运用,算法题目已经给出了提示:
小Ho:我想用集合的角度去考虑这个问题。对于每一维,比如说X维,我们能通过按X维的坐标排序,不难求出对i点来说X维比i点小的所有点的集合。现在的问题就转化成对点i来说,求出X维,Y维,Z维,Q维,W维分别比i所在那一维小的集合的交的大小。
小Hi:对!你的思路很好。但是集合大小是O(N^2),如果暴力实现的话时间复杂度就达到了O(N^2)。
小Ho:我觉得一种比较直观的方法是用一个长度为n的01串,第i位为0表示i不在集合中,1表示i在集合中。
小Hi:不错哟!那你仔细观察一下,求集合的交到底具有什么性质?比如对于n=6,集合{1,4,5}和集合{2,4,5,6}来说,它们的交是{4,5}。
小Ho:集合求交在01串中可以这么看:若两串第i位都是1,则交的串第i位是1,否则第i位就是0。这个例子中两个集合的01串分别为100110,010111。它们的交就是000110,也就是{4,5}。
小Hi:是的。我们把这个问题转化成了对01串的操作。你有没有发现,这其实类似于二进制中”and”的操作。如果我们把01串看成一个二进制的大整数,那么集合求交就变成了对两个大整数做”and”的操作。
小Ho:哈!有道理。但是这看起来复杂度似乎依旧是O(N^2)的。
小Hi:啊!但是你有没有想过,我们可以利用程序语言中的32位整数加速这个”and”,也就是说我们每32位压缩成一个32位整数,这样本来我们需要32次的操作,一下就变成了做一次位运算“并”的操作。所有我们最后的复杂度能优化成O(N^2/32)。
小Ho:原来如此!那具体怎么实现这个“压缩”的过程呢?
小Hi:其实c++/Java已经为我们设计了这样一种数据结构来解决这种问题。它的名字叫bitset/BitSet。它类似于数组,但是你可以直接对其做位运算。bitset中还有一些有用的函数,如count/cardinality可以快速算出二进制中有多少个1(这其实是一个不太好做的问题)。
程序代码
这两个版本的时间和消耗的内存差不多。
我的版本
#include <cstdio>
#include <bitset>
#include <algorithm>
using namespace std;
bitset<30005> s[30005][5];
struct Rank{
int a,b,c,d,e,id;
}r[30005];
bitset<30005> t;
int n;
bool cmp1(Rank ob1,Rank ob2){
return ob1.a < ob2.a;
}
bool cmp2(Rank ob1, Rank ob2){
return ob1.b < ob2.b;
}
bool cmp3(Rank ob1, Rank ob2){
return ob1.c < ob2.c;
}
bool cmp4(Rank ob1, Rank ob2){
return ob1.d < ob2.d;
}
bool cmp5(Rank ob1, Rank ob2){
return ob1.e < ob2.e;
}
bool cmp6(Rank ob1, Rank ob2){
return ob1.id < ob2.id;
}
int main(){
// freopen("input.txt","r",stdin);
scanf("%d", &n);
for(int i = 1; i <= n; i++)
{
scanf("%d%d%d%d%d",&r[i].a,&r[i].b,&r[i].c,&r[i].d,&r[i].e);
r[i].id = i;
}
sort(r+1,r+1+n,cmp1);
t.reset();
for(int i = 1; i <= n; i++)
{
if(i == 1)
{
t[r[i].id] = 1;
continue;
}
s[r[i].id][0] = s[r[i].id][0] | t;
t[r[i].id] = 1;
}
sort(r+1,r+1+n,cmp2);
t.reset();
for(int i = 1; i <= n; i++)
{
if(i == 1)
{
t[r[i].id] = 1;
continue;
}
s[r[i].id][1] = s[r[i].id][1] | t;
t[r[i].id] = 1;
}
sort(r+1,r+1+n,cmp3);
t.reset();
for(int i = 1; i <= n; i++)
{
if(i == 1)
{
t[r[i].id] = 1;
continue;
}
s[r[i].id][2] = s[r[i].id][2] | t;
t[r[i].id] = 1;
}
sort(r+1,r+1+n,cmp4);
t.reset();
for(int i = 1; i <= n; i++)
{
if(i == 1)
{
t[r[i].id] = 1;
continue;
}
s[r[i].id][3] = s[r[i].id][3] | t;
t[r[i].id] = 1;
}
sort(r+1,r+1+n,cmp5);
t.reset();
for(int i = 1; i <= n; i++)
{
if(i == 1)
{
t[r[i].id] = 1;
continue;
}
s[r[i].id][4] = s[r[i].id][4] | t;
t[r[i].id] = 1;
}
for(int i = 1; i <= n; i++)
s[i][0] = s[i][0]&s[i][1]&s[i][2]&s[i][3]&s[i][4];
for(int i = 1; i <= n; i++){
printf("%lld\n", s[i][0].count());
}
return 0;
}
其他版本
#include<bits/stdc++.h>
#define maxn 30050
using namespace std;
int a[maxn][5],b[5][maxn];
bitset<maxn>s[5][maxn],t; //s[i][j]:第i门课排在j-1之前的编号情况
int main(){
// freopen("input.txt","r",stdin);
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
for(int j=0;j<=4;j++){
scanf("%d",&a[i][j]);
b[j][a[i][j]]=i; //第j门课排第a[i][j]名的是编号为i的人
}
}
for(int j=0;j<=4;j++){
s[j][1]=0;
for(int i=2;i<=n;i++){
s[j][i]=s[j][i-1];
s[j][i].set(b[j][i-1]);
}
}
for(int i=1;i<=n;i++){
t=s[0][a[i][0]];
for(int j=1;j<=4;j++){
t&=s[j][a[i][j]];
}
printf("%d\n",t.count());
}
return 0;
}