【仙人掌?】【并查集】HDU6350 Always Online

本文详细介绍了如何利用仙人掌图的性质和并查集解决HDU6350题目的过程。首先分析了树形结构下的解决方案,通过从大到小加入边,根据边权更新联通块中的信息来计算最大流。接着,讨论了仙人掌图的解题策略,利用最小割最大流定理,将问题转化为树形结构,最后按照树的解决方案求解。数据范围的特殊性使得正确处理整数类型成为关键。

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

分析:

考场上看都没看的题。。。但实现起来居然异常简单(相对于隔壁D题动态点分治而言)。。。。

这题除了利用了仙人掌图的定义。。其它都和仙人掌没关系。。。

先考虑一个相对简单的问题:

如果给的是棵树,怎么求答案?

树的性质无非就是两点间路径唯一,也就是说,这里的“最大流”可以看作两点间路径上的边权最小值。

从大到小加入边。每次加入时,因为两端点所在的联通块中,这条边边权一定是最小的(比它边权小的边都还没加入)。那么这两个联通块之间的路径最小值一定可以在当前这条边上取到。所以就可以算出这条边对最终答案的贡献。

由于题目非常鬼畜地要异或点的编号,所以这里求贡献还是需要一点技巧:

用并查集维护每个点所在的联通块中,编号在二进制下第kk位为0、为1的点的数量,设为sumk0sumk1sumk1

我们可以按边权在二进制下的每一位,分别求贡献。

若边权二进制下第ii位为1,那么就要求两个点的编号在第i位的值必须相同(否则xor之后就是0了),
这部分的贡献就是(sumi0(u)sumi0(v)+sumi1(u)sumi1(v))2i(sumi0(u)∗sumi0(v)+sumi1(u)∗sumi1(v))∗2i

若边权二进制下第ii位为0,那么就要求两个点的编号在第i位的值必须不同

这部分的贡献就是(sumi0(u)sumi1(v)+sumi1(u)sumi0(v))2i(sumi0(u)∗sumi1(v)+sumi1(u)∗sumi0(v))∗2i

仙人掌图如何求答案?

这里又要利用最小割最大流定理了,找两个点之间的最大流,无非就是在这两个点之间找一条最小割。结合仙人掌图的定义:每条边最多出现在一个简单环中。那么最小割只有两种情况:删去某个环上的两条边、或删去一条树边(即不在简单环上的边)。
这里写图片描述

而且如果删去环上的边,必然会删去这个环上边权最小的一条

那么可以把每个环上边权最小的边删去,然后把其余的边全部加上这条边的权值即可。因为割去这个环上任意一条边,都会割去最小边,那么可以直接把最小边的代价加到其他边里面去,然后删去最小边。

由于每条边最多在一个简单环里,所以这里的加权操作可以直接暴力搞。

这个骚操作之后,这个图就变成一颗树了。。。。然后就可以按照上面的做法过掉了。。。

这题数据范围卡得很好啊。。unsigned long long 才能过。。。是不是考场上很多人被这个坑了所以过的人不多?

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 300010
using namespace std;
typedef unsigned long long ull;
ull ans=0;
int cnt;
int fa[MAXN];
ull siz[MAXN][32][2];
struct node{
    int u,v;
    int val;
    node () {}
    node (int u1,int v1,int val1):u(u1),v(v1),val(val1) {}
    bool operator < (const node &a) const {
        return val>a.val;
    }
}l[MAXN];
int dep[MAXN],fai[MAXN],soi[MAXN];
vector<int> a[MAXN];
vector<ull> w[MAXN];
bool used[MAXN];
void dfs(int x,int f,int id){
    dep[x]=dep[f]+1;
    soi[x]=id;
    for(int i=0;i<a[x].size();i++){
        int u=a[x][i];
        if(u==f){
            fai[x]=i;
            continue;
        }
        dfs(u,x,i);
    }   
}
void add(int x,int y,int val){
    if(x==y)
        return ;
    if(dep[x]<dep[y])
        swap(x,y);
    while(dep[x]!=dep[y]){
        int f=a[x][fai[x]];
        w[x][fai[x]]+=val;
        w[f][soi[x]]+=val;
        x=f;
    }
    while(x!=y){
        int f=a[x][fai[x]];
        w[x][fai[x]]+=val;
        w[f][soi[x]]+=val;
        x=f;

        f=a[y][fai[y]];
        w[y][fai[y]]+=val;
        w[f][soi[y]]+=val;
        y=f;
    }
}
void dfs1(int x,int f){
    for(int i=0;i<a[x].size();i++){
        int u=a[x][i];
        if(u==f)
            continue;
        l[++cnt]=node(x,u,w[x][i]);
        dfs1(u,x);
    }
}
int get_fa(int x){
    if(fa[x]==0)
        return x;
    fa[x]=get_fa(fa[x]);
    return fa[x];   
}
void merge(int x,int y,int val){
    x=get_fa(x);
    y=get_fa(y);
    for(ull i=0;i<31;i++){
        if(val&(1ull<<i)){
            ans+=siz[x][i][0]*siz[y][i][0]*(1ull<<i);
            ans+=siz[x][i][1]*siz[y][i][1]*(1ull<<i);
        }
        else{
            ans+=siz[x][i][0]*siz[y][i][1]*(1ull<<i);
            ans+=siz[x][i][1]*siz[y][i][0]*(1ull<<i);
        }
    }
    fa[x]=y;
    for(int i=0;i<31;i++){
        siz[y][i][0]+=siz[x][i][0];
        siz[y][i][1]+=siz[x][i][1];
    }
}
int main(){
    //freopen("1001.in","r",stdin);
    //freopen("1.txt","w",stdout);
    int t;
    SF("%d",&t);
    while(t--){
        int n,m;
        SF("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            fa[i]=0;
            dep[i]=0;   
            for(int j=0;j<31;j++){
                siz[i][j][0]=((i>>j)&1)^1;
                siz[i][j][1]=((i>>j)&1);
            }
            a[i].clear();
            w[i].clear();
        }
        for(int i=1;i<=m;i++){
            SF("%d%d%d",&l[i].u,&l[i].v,&l[i].val);
            used[i]=0;
        }
        sort(l+1,l+1+m);
        for(int i=1;i<=m;i++){
            int fu=get_fa(l[i].u);
            int fv=get_fa(l[i].v);
            if(fu!=fv){
                used[i]=1;
                fa[fu]=fv;
                a[l[i].u].push_back(l[i].v);
                a[l[i].v].push_back(l[i].u);
                w[l[i].u].push_back(l[i].val);
                w[l[i].v].push_back(l[i].val);
            }
        }
        dfs(1,0,0);
        for(int i=1;i<=n;i++)
            fa[i]=0;
        for(int i=1;i<=m;i++)
            if(used[i]==0)
                add(l[i].u,l[i].v,l[i].val);
        cnt=0;
        dfs1(1,0);
        sort(l+1,l+n);
        ans=0;
        for(int i=1;i<n;i++)
            merge(l[i].u,l[i].v,l[i].val);
        PF("%llu\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值