hdu6350 /// tarjan+并查集+组合+最大流思想

题目大意:

给定n个点 m条边没有重边的仙人掌图(没有一条边会同时存在与两个环 也就是环都是相互独立的)

求任意两点间 i^j^maxflow(i,j)的总和 maxflow为两点间最大流

题解:https://blog.csdn.net/du_lun/article/details/81610624

对于两点间的最大流

若是一个环上的两点 那么最大流就会等于两条路径上的最短边的总和

那么任意两点间的最大流 就是其余边加上最短边后去掉最短边 

这样这个图就变成了一棵树

此时按边长逆序排序 取出一条边求最大流 然后将两个分量合并成一个点

取出的边长就是两个联通分量间的最大流 因为每次取出的都是比之前取出的更小的边

求异或值不能n^2枚举两点 按数的32位枚举 

每次合并联通分量后 更新这个联通分量中每一个二进制位有多少0 1

当两个联通分量间的最大流

第 j 位为1时 那么使得该位异或后为1 两个联通分量的第j位应为 1 1 或 0 0

第 j 位为0时 那么使得该位异或后为1 两个联通分量的第j位应为 1 0 或 0 1

组合一下 方案数乘以 2^j 就是最大流第 j 位的贡献

 这题会爆long long 需要用unsigned long long 

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define LL long long
#define mem(i,j) memset(i,j,sizeof(i))
const int N=1e5+5;

struct EDGE {
    int u,v,nt; LL w;
    bool operator <(const EDGE& e)const {
        return w>e.w;
    }
}E[N<<2],e[N<<1],t[N<<1]; // 图的邻接表 树边集 暂存
int head[N], tot, cnt;
void addE(int u,int v,LL w) {
    E[tot].u=u, E[tot].v=v;
    E[tot].w=w, E[tot].nt=head[u];
    head[u]=tot++;
}

int low[N], ind, dfn[N], fa[N];

int pos[N]; // 记录走过的环上 i点对应的边在邻接表中的索引
void cir(int u,int v,int id) {
    int c=0, k=v;
    while(k!=u) {
        t[c++]=E[pos[k]], k=fa[k];
    } // t暂存环上的所有边
    t[c++]=E[id];
    LL mini=LLINF;
    for(int i=0;i<c;i++) // 找到最短边
        if(t[i].w<mini) mini=t[i].w, k=i;
    for(int i=0;i<c;i++) // 其他边加上最短边长 存入数边集
        if(i!=k) t[i].w+=mini, e[cnt++]=t[i];
}

void tarjan(int u,int f) {
    dfn[u]=low[u]=++ind;
    fa[u]=f;
    for(int i=head[u];~i;i=E[i].nt) {
        int v=E[i].v;
        if(v==f) continue;
        if(!dfn[v]) {
            pos[v]=i, tarjan(v, u);
            low[u]=min(low[u],low[v]);
        } else low[u]=min(low[u],dfn[v]);
    }
    for(int i=head[u];~i;i=E[i].nt) {
        int v=E[i].v;
        if(low[v]>dfn[u]) e[cnt++]=E[i]; // 树边
        // v以及其子孙节点连的所有点中dfn最小的点比u还小
        // 说明u往下有一个环且v在环中 那么此时u到v的这条边为树边
        else if(dfn[v]>dfn[u] && fa[v]!=u) cir(u,v,i); //
    }
}

int f[N];
int getf(int x) {
    if(f[x]==x) return x;
    else return f[x]=getf(f[x]);
}
int n, m;
LL c[N][35][2];
// c[i][j][0/1]是第i个联通分量二进制的第j位为0 1的数量
unsigned long long ans;
void init() {
    tot=cnt=ind=0; ans=0;
    for(int i=1;i<=n;i++) {
        head[i]=-1; f[i]=i;
        dfn[i]=0;
        for(int j=0;j<=30;j++)
            c[i][j][1]=(i>>j)&1, c[i][j][0]=((i>>j)&1)^1;
    }
}

int main()
{
    int t; scanf("%d",&t);
    while(t--) {
        scanf("%d%d",&n,&m);
        init();
        while(m--) {
            int u,v; LL w; scanf("%d%d%lld",&u,&v,&w);
            addE(u,v,w); addE(v,u,w);
        }
        tarjan(1,-1);
        sort(e,e+cnt);
        for(int i=0;i<cnt;i++) {
            int fu=getf(e[i].u);
            int fv=getf(e[i].v);
            LL w=e[i].w;
            for(int j=0;j<=30;j++)
                if(w&(1LL<<j)) {
                    ans+=c[fu][j][1]*c[fv][j][1]*(1LL<<j);
                    ans+=c[fu][j][0]*c[fv][j][0]*(1LL<<j);
                } else {
                    ans+=c[fu][j][0]*c[fv][j][1]*(1LL<<j);
                    ans+=c[fu][j][1]*c[fv][j][0]*(1LL<<j);
                }
            f[fu]=fv;
            for(int j=0;j<=30;j++)
                c[fv][j][1]+=c[fu][j][1],
                c[fv][j][0]+=c[fu][j][0];
        }
        printf("%llu
",ans);
    }

    return 0;
}
View Code
原文地址:https://www.cnblogs.com/zquzjx/p/10340472.html