bzoj2115_集合+贪心

10 篇文章 0 订阅
1 篇文章 0 订阅

xor运算的性质: a xor b xor b = a。 同一个数被xor偶数次后的结果为0.

题目要求的是一条从1到n的路径, 于是我们可以将该条路径分解为一条无环的直接路径和这条路径上连接的一部分环的结合, 这样只要求出需要的环就行了。

难道我要把所有环都求出来? (假设给出一个完全图, 想想也觉得是2^n级别的了)

机智为上策, 我们只要求出一部分环, 将这些环的权值算出来, 再利用xor运算的性质, 不就可以表示其它的环了? 如果找到了合适的环, 问题就迎刃而解。

于是问题升级为求环的方法。 对于图中的每一个点x, 记f[x]为从点1到点x的xor路径值, 那么在找到另一个已经搜索过的点后, 可以知道这两点确定一个环, 得到公式v = f[x] ^ f[k] ^ e[i].w, v表示这个环的权值。 环的值已经得到, 问题就转化成为对二进制数求xor和最大的贪心问题。

首先将ans赋为f[n], 开始枚举v中的数值。 若第一次遇到最高位为j的数, 将这个数从集合中提出来, 并将相同位置为1的剩余元素与之异或, 这样保证了结合的稳定性, 因为相当于xor了两次, 没有影响。 最终对提出的数进行贪心, 尽可能的将高位全部赋为1, ans get。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define N 50000 +10
 
using namespace std;
 
typedef long long LL;
struct edge
{
    int to, next;
    LL w;
}e[4*N];
vector<LL>v;
int n, m, num, p[N], flag[N];
LL ans, f[N], r[70];
void read(int &x)
{
    x = 0;
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9')
    {
        x = 10*x + c - '0';
        c = getchar();
    }
}
void read(LL &x)
{
    x = 0;
    char c = getchar();
    while(c < '0' || c > '9') c = getchar();
    while(c >= '0' && c <= '9')
    {
        x = 10*x + c - '0';
        c = getchar();
    }
}
void add(int x, int y, LL z)
{
    e[++num].to = y;
    e[num].w = z;
    e[num].next = p[x];
    p[x] = num;
}
void init()
{
    LL z;
    int x, y;
    num = 1;
    read(n), read(m);
    for (int i = 1; i <= m; ++i)
    {
        read(x), read(y), read(z);
        add(x, y, z);
        add(y, x, z);
    }
}
void dfs(int x, int last)
{
    flag[x] = 1;
    for (int i = p[x]; i; i = e[i].next)
    if (i != last)
    {
        int k = e[i].to;
        if (!flag[k])
        {
            f[k] = f[x] ^ e[i].w;
            dfs(k, i^1);
        }
        else v.push_back(f[x]^f[k]^e[i].w);
    }
}
void deal()
{
    dfs(1, 0);
    ans = f[n];
    int sz = v.size();
    for (int i = 0; i < sz; ++i)
    for (int j = 63; j >= 0; j--)
    if ((v[i]>>j) & 1)
    {
        if (!r[j])
        {
            r[j] = v[i];
            break;
        }
        v[i] ^= r[j];
    }
    for (int i = 62; i >= 0; i--)
    if (!((ans>>i)&1) && r[i]) ans ^= r[i];
    printf("%lld\n", ans);
}
int main()
{
    init();
    deal();
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值