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所在的并查集合并
}
...
}