HDU 5096 ACM Rank(树状数组)

题目:ACM Rank

题意:其实搞ACM的题意都知道是干嘛的啦。给定n个队伍,然后m道题。跟着q个操作,要么是队伍提交,要么是查询。

每次没有AC的罚时是20分钟,相同解题数相同罚时的队伍排名相同,可以看题目里面的表。剩下的排名跟罚时计算规则跟平常的比赛一样,我就不多说了。

1、S [minute]:[Team No]:[Problem ID]:[Result]

表示在minute分钟时,编号为Team No的队伍提交了第ID题,评测结果为Result。

首先,如果该队伍已经AC了这道题了,那么忽略这个提交,此次为无效提交。

如果该队伍上一次有效提交(不管提交了哪道题)跟此次提交的事件差距小于5分钟,那么忽略这个提交,即视此次为无效提交。

对于合法提交,Result值为1表示AC,否则没有AC。如果AC了,还要输出[Team No][Problem ID]。

2、R [Team No]

查询编号为Team No的队伍的排名

3、T [k]

查询排名为k的队伍的编号。如果有多个队伍并列,那么取最早AC最后一题的队伍。如果都是0AC的,输出最小编号。

如果不存在排名k的,输出-1。比如题目给的图片就没有4、6、9等等。

题意很好懂,就是怎么实现。如果每次都去重新排序,10000个队伍+100000次查询更新是行不通的。

由于题目最多是10题,所以可以开按照解题数开10个树状数组。每个数组里面按照罚时来存,比如说某个队伍当前AC了1题,罚时为30分钟,那么就在第1个树状数组的30这个位置加1。如果它再AC一题,就先把这个30的1减掉,然后更新到第2个树状数组的对应罚时位置。

那么罚时最多是多少呢?粗略算的,10道题都在300分钟AC(虽然这不可能),由于5分钟才能交一次,即使0分钟开始交最多也就交61次,所以罚时数不超过3000+1206<5000,当然你还可以算的更精细些。我就按着5000来开了。

对于查询某个队伍的排名。我们可以用sum[i]表示当前解出i道题的队伍的数量,那么根据当前队伍的解题数x,先把解题数比它多的加起来,由于最多10个题,暴力算也相当于常数操作。跟着在第x个树状数组里面,求出比它罚时少的队伍数,全部加起来再加上1就是当前队伍的排名。

sol_num表示至少解出1题的队伍数。

solved[id]表示队伍的解题数。

int R(){
	int id, res;
	in(id);
	if(solved[id]==0)	return sol_num+1;
	res = cal(solved[id], pen[id]-1)+1;
	for(int i=solved[id]+1; i<=m; i++)	res+=sum[i];
	return res;
}
比较麻烦是第3个查询。

如果k大于队伍数,输出-1。

如果k大于sol_num,那么解题数为0的排名都是sol_num+1,如果k等于sol_num+1,就输出还未AC的队伍的最小编号,否则还是输出-1。

排除上面的,我们可以确定排名k的队伍是某个有解题的队伍,但不一定存在k这个排名,有可能出现并列覆盖掉k的。

我们可以先枚举解题数,找到排名k的队伍的解题数i。

然后在第i个树状数组二分出排名k的罚时x。我们可以利用第二个查询的方法,求出解出i道题罚时为x的实际排名。

如果这个排名就是k,那么我们就输出解出i道题,罚时为x的,最早AC最后一题的队伍编号,否则输出-1。

至于这个队伍的求法,我是用map<int, set<Team> >,对应解题数开10个map,每个map都是罚时到队伍集合的映射。即根据解题数跟罚时可以找到这个队伍集合,集合里面的队伍按照AC最后一题的先后排序,那么取最左边的值即可。


剩下一些维护的工作就是要维护好map和树状数组。一个队伍如果成功AC一道题,记得先把它原先在树状数组的位置去除,从原先所在的map集合里面删除,然后添加到新的位置去。

#include<cstdio>
#include<cstring>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
const int N = 100100;
const int M = 5000;
int n, m, sol_num, tot;
int arr[11][M];//树状数组
int cnt[N][11];//每个队伍每道题需要记录的WA的次数
int sum[11];//解出i道题的队伍数
bool solve[N][10];//solve[i][j]表示i队是否解出第j题
char s1[20], s2[20];
int solved[N];//队伍的解题数
int pen[N];//队伍的罚时
int last[N];//队伍最近一次有效提交的事件
struct Team{
	int id, ac;//ac代表最后一次ac,在查询里面的编号
	Team(){}
	Team(int id, int ac):id(id),ac(ac){}
	bool operator < (const Team &A)const{
		return ac<A.ac;
	}
};
int lowbit(int x){
	return x&(-x);
}
void add(int sol, int x, int v){
	if(x==0){//有可能出现罚时为0的情况
		arr[sol][x]+=v;
		return;
	}
	for(; x<M; x+=lowbit(x))	arr[sol][x]+=v;
}
int cal(int sol, int x){
	if(x<0)	return 0;
	int res = arr[sol][0];//有可能出现罚时为0的情况
	for(; x>0; x-=lowbit(x))	res += arr[sol][x];
	return res;
}
Team t[N];
map<int, set<Team> > MP[11];
set<int> ST;//用来存放AC数为0的队伍编号。
void init(){
	for(int i=1; i<=m; i++){
		MP[i].clear();
	}
	ST.clear();
	for(int i=0; i<n; i++){
		ST.insert(i);
		t[i] = Team(i,-10);
		last[i] = -10;
	}
	tot = 0;
	sol_num = 0;
	memset(solved, 0, sizeof(solved));
	memset(pen, 0, sizeof(pen));
	memset(arr, 0, sizeof(arr));
	memset(solve, 0, sizeof(solve));
	memset(cnt, 0, sizeof(cnt));
	memset(sum, 0, sizeof(sum));
}
inline void in(int &x){
	char c=getchar();
	x = 0;
	while(c<48 || c>57)	c=getchar();
	while(c>=48 && c<=57){
		x = x*10+c-48;
		c = getchar();
	}
}
inline void read(int &x){
	char c=getchar();
	x = c-'A';
}
void submit(){
	int min, id, flag, pro;
	in(min);
	in(id);
	read(pro);
	in(flag);
	if(min - last[id] < 5 || solve[id][pro])	return;
	last[id] = min;
	if(flag){
		printf("[%d][%c]\n", id, pro+'A');
		int sol = solved[id];
		int penlty = pen[id];
		if(sol){
			MP[sol][penlty].erase(t[id]);
			add(sol, penlty, -1);
			sum[sol]--;
		}
		else{
			sol_num++;
			ST.erase(id);
		}
		sol++;
		penlty += min + cnt[id][pro]*20;
		solved[id]++;
		pen[id] = penlty;
		t[id].ac = ++tot;
		MP[sol][penlty].insert(t[id]);
		add(sol, penlty, 1);
		solve[id][pro]=1;
		sum[sol]++;
	}
	else{
		cnt[id][pro]++;
	}
}
int R(){
	int id, res;
	in(id);
	if(solved[id]==0)	return sol_num+1;
	res = cal(solved[id], pen[id]-1)+1;
	for(int i=solved[id]+1; i<=m; i++)	res+=sum[i];
	return res;
}
int find(int sol, int rank){
	int low=0, top=M, mid;
	int ans = M;
	while(low<=top){
		mid = (low+top)>>1;
		int res = cal(sol, mid);
		if(res >= rank){
			ans = min(ans, mid);
			top = mid-1;
		}
		else{
			low = mid+1;
		}
	}
	int tmp = cal(sol, ans-1);
	if(tmp + 1 != rank)	return -1;
	return MP[sol][ans].begin()->id;
}
int T(){
	int rank;
	in(rank);
	if(rank > n)	return -1;
	if(rank == sol_num+1)	return *ST.begin();
	if(rank > sol_num)	return -1;
	int i, res = 0;
	for(i=m; i>=0; i--){
		res += sum[i];
		if(res >= rank)	break;
	}
	res -= sum[i];
	return find(i, rank - res);
}
int main(){
	while(~scanf("%d %d", &n, &m)){
		init();
		while(~scanf("%s", s1) && s1[0]!='C'){
			if(s1[0]=='S')	submit();
			else if(s1[0]=='T')	printf("%d\n", T());
			else	printf("%d\n", R());
		}
		scanf("%s", s1);
		puts("");
	}
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值