【洛谷】【P4570】元素(贪心、线性基)

传送门:元素        贪心        线性基


题目描述

相传,在远古时期,位于西方大陆的 Magic Land 上,人们已经掌握了用魔法矿石炼制法杖的技术。那时人们就认识到,一个法杖的法力取决于使用的矿石。

一般地,矿石越多则法力越强,但物极必反:有时,人们为了获取更强的法力而使用了很多矿石,却在炼制过程中发现魔法矿石全部消失了,从而无法炼制出法杖,这个现象被称为“魔法抵消” 。特别地,如果在炼制过程中使用超过一块同一种矿石,那么一定会发生“魔法抵消”。后来,随着人们认知水平的提高,这个现象得到了很好的解释。经过了大量的实验后,著名法师 Dmitri 发现:如果给现在发现的每一种矿石进行合理的编号(编号为正整数,称为该矿石的元素序号),那么,一个矿石组合会产生“魔法抵消”当且仅当存在一个非空子集,那些矿石的元素序号按位异或起来为零(如果你不清楚什么是异或,请参见下一页的名词解释 )。

例如,使用两个同样的矿石必将发生“魔法抵消”,因为这两种矿石的元素序号相同,异或起来为零。并且人们有了测定魔力的有效途径,已经知道了:合成出来的法杖的魔力等于每一种矿石的法力之和。人们已经测定了现今发现的所有矿石的法力值,并且通过实验推算出每一种矿石的元素序号。

现在,给定你以上的矿石信息,请你来计算一下当时可以炼制出的法杖最多有多大的魔力。


输入格式

第一行包含一个正整数 N,表示矿石的种类数。

接下来 N 行,每行两个正整数Numberi​ 和 Magici​,表示这种矿石的元素序号和魔力值。


输出格式

仅包含一行,一个整数代表最大的魔力值。


题解

 第一眼看到这道题,首先想到贪心,因为所有元素中能促成“魔法抵消”的非空子集是固定的,不固定的是这些子集中被选择的矿石,把所有矿石按法力值从大到小进行排序,遍历数组进行check操作,如果该矿石被遍历时加入它不会造成“魔法抵消”则加入矿石,否则遍历下一个矿石。

现在的问题就变成了:如何快速判断矿石能否被加入。

考虑到Number的值很大,不能暴力,想到能否用二进制位数来简化操作,于是发现可以用线性基处理数据。

这样问题就都解决了,下面是代码解释和代码。

1. 数据结构和输入

  • 定义了一个 struct St,其中有两个属性:Number 和 MagicNumber 是我们要处理的数字,而 Magic 是这个数字的权重。
  • 使用 bool operator< 来实现排序的自定义规则,即根据 Magic 值进行降序排序。排序是为了在最大化魔法值时优先考虑较大的权重。

2. 主逻辑

1) 输入数据
  • 从标准输入读取数字的数量 N,然后读取 N 个 Number 和 Magic 值,并保存在 st 数组中。
2) 排序
  • 对结构体数组 st 按照 Magic 值进行降序排序。这保证了我们在后续处理中能优先考虑魔法值大的数字。
3) 检查线性无关性
  • 使用 check 函数来决定是否可以将当前的 Number 添加到我们的线性基中。这个函数尝试将数字与已有的基进行线性组合。
    • 通过遍历从高位到低位的二进制位,使用 dp[i] 数组来存储目前已经建立的线性基。
    • 如果在某一位没有已有的基元素(即 dp[i] 为 0),那么就把当前数字 x 加入基,并返回 true
    • 若在当前位已经有了基元素,那么就用当前数字与这个基元素异或,尝试降低数字的复杂性。
4) 计算总魔法值
  • 遍历排序后的结构体数组 st
    • 对于每个数字,调用 check 函数,若它可以成功加入线性基,则累加它的魔法值到 ans 中,表示我们成功选择了这个数字。

3) 输出结果

  • 最后,打印总的魔法值 ans

#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
ll dp[64];
struct St{
    ll Number,Magic;
    bool operator<(const St &t)const{
        return Magic>t.Magic;
    }
}st[1006];
bool check(ll x){
    for(int i=61;i>=0;i--){
        if(x>>i){
            if(dp[i+1]==0){
                dp[i+1]=x;
                return true;
            }
            else {
                x^=dp[i+1];
            }
        }
    }
    return false;
}
int main(){
    int N;
    cin>>N;
    for(int i=1;i<=N;i++){
        cin>>st[i].Number>>st[i].Magic;
    }
    sort(st+1,st+N+1);
    ll ans=0;
    for(int i=1;i<=N;i++){
        if(check(st[i].Number))ans+=st[i].Magic;
    }
    cout<<ans;
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值