超级公牛冠军赛(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;
}