最小生成树专项
A
求最短路啊
那显然只需要看端点颜色不同的边即可
那么依次考虑每条边的贡献
一个想法是暴力修改,不过菊花就死了
一个想法是把颜色相同且相连的点缩在一起然后求剩下边的min,现在至少剩下两个连通块
那根据Boruvka知道,这剩下的最优边必然是MST上的边(对于n个点任意划分为若干个集合,不同集合两两之间最小边的最小值一定在MST上)
建出Kruskal重构树?一个点造成贡献当且仅当子树内颜色个数不少于2?
也不太好感觉,可以支持加不支持删
暴力的话就是边太多就死了
由于我们求了MST,不妨一个点维护其到父亲的边的可行性,那么可以类比点分树的计算方法,我们每个点维护到儿子的所有边的贡献,这样可以将点权影响的边变为只需要整体改动和单点修改。
发现边应当按照颜色分类,所以我们利用 set map<int,multiset<int>>
存下每个点的每个儿子的边按照颜色分类后的边权最值。
这样的话,答案可以对于每个点单独算,而更改一个点的颜色,相当于是更改了那个点的2个set
对答案的贡献状态,可以 \(O(\log n)\) 做,而考虑对父亲的也是单点修改。
B
考虑支配关系
如果有 \(a<b<c\) 其两两 \(\gcd\) 相同,则显然用 \(a-b,a-c\) 即可,也即 \(b-c\) 是无用的
这就被支配了
枚举 \(\gcd\) 即可
有 \(O(n\log n)\) 条边
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\),还剩下 \(a_1\sim a_m\) 满足 \(a_i\& x=x\)。
则必然有 \(\forall i,j,a_i\&a_j=x\),否则已经提前处理。
且由于这个条件,可以推出 \(m\le \log V\),因为鸽巢原理( \(x\) 的零位最多每个位放一个数到极限了)。
所以可以暴力找扫一遍所有数合并即可。
找数这个步骤,可以在做 Kruskal 的同时做高维后缀和。
当然你可以 Boravka,朴素的想法是计算高维后缀和找最优决策,事实上 Trie
处理按位与往往将“1”子树与“0”子树合并在一起作为新的零子树,按位或同理(像线段树合并一样)每个点最多合并 \(\log V\) 次。
那么相当于每个点有颜色,你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\) 里每一列的行号不同,且满足后一定可行,否则就没办法重排。
所以我们只需要做 \(A\to B\) 这一步就好了,满足只移动每一行,然后使得每一列元素行号不同。
有点匹配的味道了
我们只关心每个元素的行号,要通过移动每一行是的每一列的元素值互不相同。
如果建立行列二分图,相当于是每一个列到每一行的边都染为不同颜色
而这个操作移动操作相当于是交换了一个行的两条出边。
你考虑依次调整每个颜色
这里显然有对称性,所以你的开始位置不重要
不过这里也可以看作行号相同的两个点不在同一列上
如果说割开看,相当于是把行号相同的 \(m\) 个元素拿出来,考虑一次性归位一种颜色,然后把 \(m\) 列拿出来组成一个二分图匹配,做 \(n\) 轮就行了,而且这玩意是正则图?
这玩意有后效性应该假了。
那么考虑每次求一列的答案
你对行和颜色建立二分图,行i向其包含的颜色 \(j\) 连接 \(c_{i,j}\) 条边表示该行颜色出现次数。
匹配后就可以拿出一列的答案,如果匹配完美
而且匹配完了之后左部右部每个点的度数全部减少 \(1\),且初始度数全部是 \(n\),局面本质不会改变(霍尔定理始终满足(每个点度数相同,又显然有 \(|E(N(S))|\ge |E(S)|\),而 \(\forall S, |E(S)|=k·|S|\),其中 \(k\) 是常数,所以有 \(|N(S)|\ge |S|\),随便拿出一组匹配之后是没有后效性的)),那就完了。
事实上这是正则图。
建立 \(k\) 正则二分图,随意拿出一组完美匹配不会影响局面,只会变为 \(k-1\) 正则二分图
不知道这个说法对不对,反正感觉说出来挺舒服,这个东西好像叫正则二分图 \(k\) 染色
输出方案真恶心。。。
使用网络流可以做到 \(O(m·(n\sqrt {n^2}))=O(mn^2)\)。
H
二分图匹配模板题。