COCI2014/2015CONTEST #3 honi&stogovi(LCA)

本文深入探讨了算法优化和数据结构应用的关键技巧,通过实例分析了如何利用区间求和、拓扑排序、并查集等算法解决复杂问题。特别强调了在处理排序、查找、统计等场景中,优化代码效率的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第四题HONI:

给出n位选手,以及每位选手两场比赛的得分。如果一位选手的两场比赛都严格大于另一位选手的两场比赛的得分,那么他第三场比赛成绩也严格大于那位选手的得分。如果两位选手的比赛总分相等,排名不分先后。求n位选手可能的排名。

模拟赛的时候实在想得太多太多了,居然用了拓扑排序- -,然后暴搜方案数;最后想到了严格大于,结果没有想到区间求和委屈,实在遗憾啊!

正解是统计二维区间前缀和。最先用树状数组写了一遍,结果超时,后来再看看数据范围,每一场比赛的分数不超过650,于是直接统计前缀和。如果对于选手i,第一场分数记为a[i],第二场分数记为b[i],分数比他严格小的人有x人,分数比他严格大的有y人,那么他最坏的排名不会超过n-x,最好的排名不会优于y+1,于是他的排名区间就是[y+1,n-x],所以对于每一个人,我们在标记表格k[a[i]][b[i]]的值加1,代表这个分数的人数,然后统计k数组的前缀和,得到的sum[p][q]就是第一场分数小于等于p,第二场分数小于等于q的总人数。

写到这里之后,我还是WA了,

因为,特别要注意一点的是,如果第i位选手,他的第一场分数a[i]=650(最高分),那么他的排名可能与其他第一场比赛为0分,第二场比赛为b[i]分的人并列:做最坏的打算,第i位选手第三场比赛为0分,另外的选手第三场比赛650分。我们在统计严格小于的排名的时候是考虑不到这样的情况的!

比如:有两位选手,第一位选手分数为650 1 第二位选手分数为0 1,那么第一位选手的排名就是[1,1],第二位选手的排名就是[1,2]。(理解出现障碍了,对吧!)

#include<cstdio>
#define MAXN 650
using namespace std;
int n,a[500010][2];
int k[MAXN+5][MAXN+5];
int sum[MAXN+5][MAXN+5];
int main()
{
    scanf("%d",&n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d",&a[i][0],&a[i][1]);
		k[a[i][0]][a[i][1]]++;
    }
	for(int i = 0; i <= MAXN; i++)
	{
		for(int j = 0; j <= MAXN; j++)
		{
			sum[i][j] = k[i][j];
			if(i > 0) sum[i][j] += sum[i-1][j];
			if(j > 0) sum[i][j] += sum[i][j-1];
			if(i > 0&&j > 0) sum[i][j] -= sum[i-1][j-1];
		}
	}
    for(int i = 1; i <= n; i++)
    {
        int t1,t2;
		t1 = sum[a[i][0]-1][a[i][1]-1] + k[a[i][0]][0]*(a[i][1] == MAXN) + k[0][a[i][1]]*(a[i][0] == MAXN);//严格小于,当有一场的分数为650的最高分时
		//另外一场分数为b[i]另一场为0分的人永远也不可能超过他
		t2 = sum[650][650] - sum[650][a[i][1]] - sum[a[i][0]][650] + sum[a[i][0]][a[i][1]];//严格大于
		printf("%d %d\n",t2+1,n-t1);
    }
}

第五题STOGOVI:

Mirko在玩堆栈游戏。开始他有一个空的堆栈,编号为0.在第i步(1<=i<=300000),他会选择一个编号为v的堆栈,复制一份并做如下操作:
1.a v 表示将v号堆栈复制一份,新栈的编号为i,并将元素i压入新栈的栈顶。
2. b v 表示将v号堆栈复制一份,新栈的编号为i,将新栈的栈顶元素弹出。
3.c v w 将v号堆栈复制一份,编号为i,并比较第v号和第w号堆栈中有多少相同的数。

模拟赛的时候抽风,直接写了vector来模拟,然后各种爆内存超时。

其实仔细看一下就会发现,这就是一道在线的LCA的题目。对于a操作,就是新增一个节点i,他是v的儿子节点;对于操作b,就是求节点v属于哪个节点的集合,然后把节点i放入节点v的父亲节点集合;然后操作c就是把节点i放入节点v的集合,然后求v,w最近公共祖先的深度。

#include<cstdio>
#include<iostream>
#include<cstring>
#define MAXN 300010
using namespace std;
int belong[MAXN];//相当于是一个并查集
int p[MAXN][20];
int dep[MAXN];
int n,v,w;
char ops[3];
int lca(int a,int b)
{
    int i,j;
    if(dep[a] < dep[b]) swap(a,b);
    for(i = 0; (1<<i) <= dep[a]; i++);
    i--;
    for(j = i; j >= 0; j--)
        if(dep[a] - (1<<j) >= dep[b])
            a = p[a][j];
    if(a == b) return a;
    for(j = i; j >= 0; j--)
        if(p[a][j] != -1&&p[a][j] != p[b][j])
        {
            a = p[a][j];
            b = p[b][j];
        }
    return p[a][0];
}
int main()
{
    memset(p,-1,sizeof p);
    scanf("%d",&n);
    for(int i = 1; i <= n; i++)
    {
        scanf("%s%d",ops,&v);
        v = belong[v];
         
        if(ops[0] == 'a')//增加新的节点
        {
            belong[i] = i;
            dep[i] = dep[v] + 1;
            p[i][0] = v;
            for(int j = 1; j <= 20; j++)//宁可多循环几次,最先按照模板写成了(1<<j)<=20
                if(p[i][j-1] != -1) p[i][j] = p[p[i][j-1]][j-1];
        }
        else if(ops[0] == 'b')//复制并删除,即该栈并入他的父亲的集合
        {
            printf("%d\n",v);
            belong[i] = p[v][0];
        }
        else //找出两点的最近公共祖先,并求深度
        {
            scanf("%d",&w);
            w = belong[w];
            belong[i] = v;
            printf("%d\n",dep[lca(v,w)]);
        }
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值