【杂题】超级公牛冠军赛(最大生成树)

本文介绍了一种解决超级公牛冠军赛问题的方法,通过构建最大生成树算法确定最佳比赛安排,实现比赛总得分的最大化。

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

超级公牛冠军赛(Superbull)

时间限制:1s 内存限制:128MB

问题背景

Bessie 和她的朋友们正在打 hootball 的年度 Superbull 冠军赛,农夫 John 则负责让比赛尽可能的刺激。

问题描述

一共有 N 支队伍参加了 Superbull 大赛(1 <= N <= 2000),每支队伍都有一个独一无二的队伍 ID 来与其他队伍区分。Superbull 实行淘汰赛制,每一轮比赛过后,农夫 John 会决定哪支队伍会被淘汰,被淘汰的队伍也就不能再参加接下来的比赛。最后,Superbull 大赛只会有一支队伍留到最后。

农夫 John 在比赛中发现了一个关于分数的不同寻常的规律。每场比赛中,两只队伍的分数和总是等于两个队伍的 ID 号 XOR 后的结果。比如 12 号队和 20 号队进行了比赛,这场比赛中总共会有 24 个得分,因为 01100 XOR 10100 = 11000。

农夫 John 相信每场比赛中的总得分越多,比赛就会显得更加刺激。因此他想要选择一种比赛安排方案使得 Superbull 整个大赛中的所有比赛场次的总得分最高。请帮助农夫 John 来组织这次大赛。

输入描述

第一行是一个整数 N,表示总共参加比赛的队伍数量。 接下来 N 行每行给出每个队伍的ID 号,所有 ID 号均处于 1...2^30-1 的区间。

输出描述

输出一个整数,为整个 Superbull 大赛中可能得到的最大的总得分。

样例输入

4

3

6

9

10

样例输出

37

样例说明

以下这种方案可以获得 37 分的总得分。

队伍 3 和 9 进行比赛,让 9 胜出。再让队伍 6 和 9 比赛,让 6 胜出。最后队伍 6 和 10 比赛,让 10 胜出。总得分为 (3 XOR 9) + (6 XOR 9) + (6 XOR 10) = 10 + 15 + 12 =37。

附加说明

XOR 运算,一般表示为^符号,是一种位运算。

1XOR 1= 0,1 XOR 0 =1,0 XOR 1 =1,0 XOR 0 = 0。

---------------------------------------------------------------------------------------------------------------

解析:

这道题一眼看去好像是一道DP题或贪心题,实则不然。

仔细研究题意,可以发现总共要进行N-1次比赛,两支队伍比赛会产生一个数值,且要求总数值最大,而且每次比赛总有一支队伍会被淘汰,因此不可能形成环。这不就是一道典型的最大生成树嘛!

两个队伍之间的边权即为他们的异或值,这样做一遍最大生成树,不就求得结果了吗?

话不多说,直接上代码。

---------------------------------------------------------------------------------------------------------------

代码:

#include<bits/stdc++.h>
#define N 2000
using namespace std;
intn,a[N+5],f[N+5];//n表示队伍支数,a存储每个队伍的ID号,f是并查集中存储每支队伍father的数组
long long ans;//最终答案可能会爆int,所以最好开long long
struct w//kruskal最小生成树
{
       int x,y;//存储每条边的两个端点
       long long v;//存储每条边的边权
}s[N*N+5];//总共有N方条边,所以要开N*N
void read(int &x)//读优
{
    x=0;int f=1;char c=getchar();
    while((c<'0'||c>'9')&&c!='-') c=getchar();
    if(c=='-') f=-1,c=getchar();
    while(c>='0'&&c<='9') (x*=10)+=c-'0',c=getchar();
    x*=f;
}
void write(long long x)//输优
{
       if(x<0) putchar('-'),x=-x;
       if(x>9) write(x/10);
       putchar(x%10+'0');
}
int getfa(int x) {return f[x]==x?x:(f[x]=getfa(f[x]));}//getfa()函数,用于在并查集中寻找每个队伍的father
bool cmp(w x,w y)//排序时要用的bool函数
{
       return x.v>y.v;//排序时比较两个点的边权,由于是最大生成树,所以从大到小排序,打大于号
}
void init()//读入
{
       read(n);
       for(int i=1;i<=n;i++) read(a[i]);
}
void work()//主要程序
{
       int k=0;//k记录当前的边数
       for(int i=1;i<=n;i++) or(int j=i;j<=n;j++) s[++k].v=a[i]^a[j],s[k].x=i,s[k].y=j;//把所有的边都记录下来
       sort(s+1,s+k+1,cmp);//排序s数组
       for(int i=1;i<=n;i++) f[i]=i;//一开始每支队伍的father都是自己
       int t=1;//表示已连边数,由于只连n-1条边,故初始化为1,判断时可以直接判断是否已等于n
       for(int i=1;i<=k;i++)
       {
              int fx=getfa(s[i].x),fy=getfa(s[i].y);//找到两个端点的最早祖先
              if(fx!=fy)//如果两点没连在一起
              {
                     f[fx]=fy;//把两点连在一起
                     ans+=s[i].v;//把ans加上这条边的边权
                     if(++t==n) return;//如果计数器t已经达到n,就立刻退出函数
              }
       }
}
int main()
{
       init();
       work();
       write(ans);
       return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值