luogu1525 关押罪犯

luogu1525
普及+ / 提高

题面:
N(N<20000)个点,M(M<100000)条边。每条边有一个权值。将图分成两部分,删除跨越两部分的边,使整个图内权值最大的边最小。

输入输出样例

思路:
有权并查集
1、将各边按照从大到小的顺序排序(优先队列)

struct tEdge()
{
    ...
    inline void addEdge(...) {...}
    bool operator < (const tEdge& rhs) const { retur w>rhs.w; } //重载运算符
};

priority_queue <tEdge> q; //优先队列

int main()
{
    ...
    tEdge tmp;
    tmp.addEdge(x, y, w);
    q.push(tmp);
    ...
}

2、并查集

struct tNode
{
    int fa, len, size; //fa:父亲; len:权值, 0代表和fa在同一部分, 1代表和fa不在同一部分; size:并查集的大小,用于按秩合并
    inline void init(int i) { fa = i; len = 0; size = 1; } //初始化
} node[MAXN];

inline void makeSet(int s) { for(int i=1; i<=s; i++) node[i].init(i); } //初始化并查集

find函数,使用了路径压缩,在路径压缩时将权值更新:

inline int find(int x)
{
    if(node[x].fa == x) return x;
    int tmpfa = node[x].fa; //记录原来的father,以便更新权值
    node[x].fa = find(node[x].fa); //路径压缩
    node[x].len ^= node[tmpfa].len; //更新权值, 1^1 = 0, 1^0 = 1, 0^0 = 0
    return node[x].fa;
}

unionSet函数, 合并时也要注意更新权值:

void unionSet(int x, int y)
{
    int rx = x, ry = y; //记录原来的x y
    x = find(x); y = find(y); //找到x,y的祖先,并将路径压缩, 更新了权值
    int w = node[rx].len ^ node[ry].len ^ 1; //因为要使x和y在不同部分中, 所以无论是将x合并至y还是将y合并至x, 权值都是相同的; node[rx].len^node[ry].len计算出了x与x的祖先的情况与y与y的祖先的情况是否一致, 若一致, 则x的祖先与y的祖先不在同意部分, 反之则在同意部分,故再异或上1
    if(x == y) return ;

    if(node[x].size > node[y].size)
    {
        node[y].fa = x;
        node[y].len = w;
        node[x].size += node[y].size;
    }
    else 
    {
        node[x].fa = y;
        node[x].len = w;
        node[y].size += node[x].size;
    } //按秩合并, 并将信息更新
    return;
}

3、判断最小的最大值
因为要让最大值最小,所以可以按边的权值从大到小的顺序枚举。当枚举到一条边,且该边的两个节点属于同意部分时,则这条边无可避免的被分到了同一部分中,答案即是这条边的权值:

int main()
{
    ...
    while(!q.empty)
    {
        ...
        int ra = a, rb = b;
        a = find(a); b = find(b);
        if(a == b) //如果a、b的祖先相同
        {
             if(node[ra].len != node[rb].len) continue; //如果a、b不在同一部分,则继续
            ans = c; 
            break; //否则直接跳出循环
        }
        else unionSet(ra, rb); //如果a、b的祖先不同, 则将a与b所在的并查集合并
    }
    ...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值