最小生成树专项

contests-link

A

求最短路啊

那显然只需要看端点颜色不同的边即可

那么依次考虑每条边的贡献

一个想法是暴力修改,不过菊花就死了

一个想法是把颜色相同且相连的点缩在一起然后求剩下边的min,现在至少剩下两个连通块

那根据Boruvka知道,这剩下的最优边必然是MST上的边(对于n个点任意划分为若干个集合,不同集合两两之间最小边的最小值一定在MST上

建出Kruskal重构树?一个点造成贡献当且仅当子树内颜色个数不少于2?

也不太好感觉,可以支持加不支持删

暴力的话就是边太多就死了

由于我们求了MST,不妨一个点维护其到父亲的边的可行性,那么可以类比点分树的计算方法,我们每个点维护到儿子的所有边的贡献,这样可以将点权影响的边变为只需要整体改动和单点修改

发现边应当按照颜色分类,所以我们利用 set map<int,multiset<int>> 存下每个点的每个儿子的边按照颜色分类后的边权最值。

这样的话,答案可以对于每个点单独算,而更改一个点的颜色,相当于是更改了那个点的2个set 对答案的贡献状态,可以 O(logn) 做,而考虑对父亲的也是单点修改。

B

考虑支配关系

如果有 a<b<c 其两两 gcd 相同,则显然用 ab,ac 即可,也即 bc 是无用的

这就被支配了

枚举 gcd 即可

O(nlogn) 条边

C

根据差分的知识,原数组全零相当于差分数组全零,那么可以对于每个点求出 l,r,则可以修改 l,r+1 两个单点(联动修改)

则所有修改的单点连通即可,跑 Kruskal 就 OK 了

D

没啥好说的,线段树分治,每一层扔掉不可能的边和一定能的边即可,其余递归解决,复杂度双 log

不可能的边指:假定子树内将来会用到的所有边边权全是 inf,也不可能用到的边(也就是本节点涉及的边跑 Kruskal,扔掉无法更新的)。

一定能的边指:假设子树内将来会用到的边边权全是 inf,也会用到的边(也就是先加入所有的将来边,然后再跑Kruskal仍然能够产生贡献的边)

显然,这样处理之后剩下的边只有 O(len) 条,下传到左右儿子即可。

很经典。

void sol(int x,int l,int r){
    sort(ad[x].begin(),ad[x].end());
    if(l==r){
        ll lst=dsu::top,tmp=res;
        for(auto [u,v,w]:ad[x])res+=dsu::merge(u,v)*w;
        ans[l]=res;res=tmp;dsu::del(lst);return ;
    }
    ll lst=dsu::top,tmp=res,mid=l+r>>1;
    vector<bool>ud(ad[x].size());
    for(auto [u,v,w]:pas[x])dsu::merge(u,v);
    for(int i=0;i<ad[x].size();++i){
        auto [u,v,w]=ad[x][i];
        if(dsu::merge(u,v))ud[i]=1;
    }
    dsu::del(lst);
    for(int i=0;i<ad[x].size();++i){
        if(ud[i]){dsu::merge(ad[x][i].u,ad[x][i].v);res+=ad[x][i].w;continue;}
        if(dsu::merge(ad[x][i].u,ad[x][i].v)){
            ad[lc].push_back(ad[x][i]);
            ad[rc].push_back(ad[x][i]);
        }
    }
    dsu::del(lst);
    for(int i=0;i<ad[x].size();++i)if(ud[i])dsu::merge(ad[x][i].u,ad[x][i].v);
    ad[x].clear();ad[x].shrink_to_fit();pas[x].clear();pas[x].shrink_to_fit();
    sol(lc,l,mid);sol(rc,mid+1,r);
    res=tmp;dsu::del(lst);
}

E

显然对于值相同的两个点连在一起是最优的,所以可以去掉相同值的贡献,也就是每个值只保留一个元素。

然后由于求最大生成树,考虑Kruskal,从大到小枚举边权,能连的一定连。

则假设现在枚举的边权是 x,还剩下 a1am 满足 ai&x=x

则必然有 i,j,ai&aj=x,否则已经提前处理。

且由于这个条件,可以推出 mlogV,因为鸽巢原理( x 的零位最多每个位放一个数到极限了)。

所以可以暴力找扫一遍所有数合并即可。

找数这个步骤,可以在做 Kruskal 的同时做高维后缀和。

当然你可以 Boravka,朴素的想法是计算高维后缀和找最优决策,事实上 Trie 处理按位与往往将“1”子树与“0”子树合并在一起作为新的零子树,按位或同理(像线段树合并一样)每个点最多合并 logV 次。

那么相当于每个点有颜色,你Trie维护树内不同颜色个数即可。

    for(int i=(1<<d)-1;i>=0;--i){
		if(!a[i]){
			for(int j=0;j<d;++j)if(a[i|(1<<j)]){
				a[i]=a[i|(1<<j)];break;
			}
		}
		if(!a[i])continue;
		for(int j=0;j<d;++j)if(a[i|(1<<j)]&&find(a[i])!=find(a[i|(1<<j)])){
			ans+=i;f[find(a[i])]=find(a[i|(1<<j)]);
		}
	}

F

首先可以给边赋权之后转化为WQS二分板子

另一个巧妙的想法是拿出必须边,也就是假定用所有白边后仍然需要的黑边,用所有黑边后仍然需要的白边。

这样的必经边拿出来后我们就能够保证连通了,接下来贪心先处理白边,在次数充足时贪心能加就加,次数用完了就加黑边即可。

G

将一个值的 x,y 拿出来,相当于是做变换

C 时,每个值的 x 已经归位,也就是每个行号已经OK了

那么可以发现得到一个 B 之后是可以求出 C 的,因为变换已经固定了。

所以就必须要满足 B 里每一列的行号不同,且满足后一定可行,否则就没办法重排。

所以我们只需要做 AB 这一步就好了,满足只移动每一行,然后使得每一列元素行号不同。

有点匹配的味道了

我们只关心每个元素的行号,要通过移动每一行是的每一列的元素值互不相同。

如果建立行列二分图,相当于是每一个列到每一行的边都染为不同颜色

而这个操作移动操作相当于是交换了一个行的两条出边。

你考虑依次调整每个颜色

这里显然有对称性,所以你的开始位置不重要

不过这里也可以看作行号相同的两个点不在同一列上

如果说割开看,相当于是把行号相同的 m 个元素拿出来,考虑一次性归位一种颜色,然后把 m 列拿出来组成一个二分图匹配,做 n 轮就行了,而且这玩意是正则图?

这玩意有后效性应该假了。

那么考虑每次求一列的答案

你对行和颜色建立二分图,行i向其包含的颜色 j 连接 ci,j 条边表示该行颜色出现次数。

匹配后就可以拿出一列的答案,如果匹配完美

而且匹配完了之后左部右部每个点的度数全部减少 1,且初始度数全部是 n,局面本质不会改变(霍尔定理始终满足(每个点度数相同,又显然有 |E(N(S))||E(S)|,而 S,|E(S)|=k·|S|,其中 k 是常数,所以有 |N(S)||S|,随便拿出一组匹配之后是没有后效性的)),那就完了。

事实上这是正则图。

建立 k 正则二分图,随意拿出一组完美匹配不会影响局面,只会变为 k1 正则二分图

不知道这个说法对不对,反正感觉说出来挺舒服,这个东西好像叫正则二分图 k 染色

输出方案真恶心。。。

使用网络流可以做到 O(m·(nn2))=O(mn2)

H

二分图匹配模板题。

posted @   spdarkle  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示