很巧妙的最小生成树:
一.CF1120D Power Tree
题目大意:题目
第一眼很像个树型dp,实际上树型dp也可做。但有一个巧妙地办法:考虑到叶子节点的dfn序有序,将叶子节点排列为序列,每个节点控制这个序列的某一个区间。题目转化为对区间进行修改,使得区间所有点值为0,求修改的最小代价和方案并集。
区间修改可以想到差分,对[l,r]修改实际上是差分数组c[l]+x,c[r+1]-x,考虑最终结果的差分数组一定是c[1…n]=0(这里n是叶子节点数),所有的代价都传递给了c[n+1],其实可以把差分数组的修改过程抽象为单向的代价传递,我们希望把c[l]修改为0,代价传递给了c[r+1],所以考虑l–>r+1建边,代价为节点的w,此时求最小代价便转化为了最小生成树,为什么你,只要区间的所有点相连,那么最终所有的代价都到了n+1身上,此时的差分数组满足条件。
另外需要注意的是答案求并集,在krus的时候,要考虑相同边权的边集,以边集为单位加入答案,并且连边和加答案要分开做,这里看代码很好懂。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<vector>
#define LL long long
#define MAXN 200010
#define INF 0x3f3f3f3f
using namespace std;
struct edge{
int u,v,len,pos;
}e[MAXN];
int n,m,c[MAXN],ldfn[MAXN],rdfn[MAXN],cnt,f[MAXN],ans[MAXN],tot;
LL all_len;
vector<int> E[MAXN];
void dfs(int u,int fa){
if(E[u].size()==1&&u!=1){
ldfn[u]=rdfn[u]=++cnt;
e[++m].u=ldfn[u],e[m].v=rdfn[u]+1,e[m].len=c[u],e[m].pos=u;
return;
}
ldfn[u]=INF,rdfn[u]=-1;
for(int i=0;i<E[u].size();i++){
int v=E[u][i];
if(v==fa) continue;
dfs(v,u);
ldfn[u]=min(ldfn[u],ldfn[v]);
rdfn[u]=max(rdfn[u],rdfn[v]);
}
e[++m].u=ldfn[u],e[m].v=rdfn[u]+1,e[m].len=c[u],e[m].pos=u;
}
bool cmp(edge a,edge b){return a.len<b.len;}
int findfa(int u){return f[u]==u?u:f[u]=findfa(f[u]);}
void krus(){
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;){
int t=i;
while(e[t].len==e[t+1].len&&t+1<=m) t++;
for(int j=i;j<=t;j++){
if(findfa(e[j].u)!=findfa(e[j].v)){
tot++;
ans[e[j].pos]=1;
}
}
for(int j=i;j<=t;j++){
int fu=findfa(e[j].u),fv=findfa(e[j].v);
if(fu!=fv){
f[fu]=fv;
all_len+=e[j].len;
}
}
i=t+1;
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>c[i];
for(int i=1;i<n;i++){
int u,v; cin>>u>>v;
E[u].push_back(v);
E[v].push_back(u);
}
dfs(1,0);
krus();
cout<<all_len<<' '<<tot<<endl;
for(int i=1;i<=n;i++) if(ans[i]) cout<<i<<' ';
return 0;
}
二.P3623 [APIO2008] 免费道路
题目
很巧妙的最小生成树问题。
题目大意:
鹅卵石路边权为1,反之为0。
先跑一遍正常的最小生成树,这时被加进去为1的边一定是必要的边,证明:若跑完所有0边仍没生成树,则后面加进去的1边一定是唯一的桥,没有此桥一定连不通。
记录这些必要的边,如果 边数>k了,无解。
第二遍krus,先连必要的边,然后再补齐差的1边,即按边权从大到小跑krus,因为只要求一组解,那么差的边只要合法可以任选,可以证明一定能成树。
此题结束。
#include<bits/stdc++.h>
#define MAXN 20010
#define MAXM 100010
using namespace std;
int n,m,k,f[MAXN],flag=1,must[MAXM],ans[MAXN];
struct edge{
int u,v,len;
}e[MAXM];
bool cmp(edge a,edge b){return a.len<b.len;}
int findfa(int u){return f[u]==u?u:f[u]=findfa(f[u]);}
void krus(){
for(int i=1;i<=n;i++) f[i]=i;
int cnt=0,cnt1=0;
for(int i=1;i<=m;i++){
int fu=findfa(e[i].u),fv=findfa(e[i].v);
if(fu!=fv){
f[fu]=fv;
if(e[i].len) must[i]=1,cnt1++;
cnt++;
}
if(cnt==n-1) break;
}
if(cnt1>k) flag=0;
}
void ag_krus(){
for(int i=1;i<=n;i++) f[i]=i;
int cnt1=0;
for(int i=1;i<=m;i++){
if(must[i]){
f[findfa(e[i].u)]=findfa(e[i].v);
cnt1++;
ans[++ans[0]]=i;
}
}
for(int i=m;i>0;i--){
if(must[i]) continue;
int fu=findfa(e[i].u),fv=findfa(e[i].v);
if(e[i].len&&cnt1<k&&fu!=fv) f[fu]=fv,cnt1++,ans[++ans[0]]=i;
if(!e[i].len&&cnt1==k&&fu!=fv) f[fu]=fv,ans[++ans[0]]=i;
if(ans[0]==n-1) break;
}
if(cnt1<k) flag=0;
}
int main(){
cin>>n>>m>>k;
for(int i=1;i<=m;i++){
int u,v,w; cin>>u>>v>>w;
e[i].u=u,e[i].v=v,e[i].len=w?0:1;
}
sort(e+1,e+m+1,cmp);
krus();
if(!flag){
puts("no solution");
return 0;
}
ag_krus();
if(!flag){
puts("no solution");
return 0;
}
else for(int i=1;i<=ans[0];i++) cout<<e[ans[i]].u<<' '<<e[ans[i]].v<<' '<<!e[ans[i]].len<<endl;
return 0;
}
三.P4180 [BJWC2010] 严格次小生成树
题目
思路非常好想,找到一条不在最小生成树中的边,这时加到最小生成树中,此时有环了,在这个环中找到次大的那条边删掉,为什么要次大,因为是严格次小生成树,如果环中有与这个加入边值相等的边,那么我们需要次大的那一个,所以要维护一条链上的最大值和次大值,考虑倍增,求LCA,同时维护链上的最大和次大值即可。
#include<bits/stdc++.h>
#define LL long long
#define MAXN 100010
#define INF 0x7fffffff
using namespace std;
struct edge{
int u,v,len;
}e[MAXN*3];
struct Edge{
int to,len;
};
vector<Edge> E[MAXN];
int n,m,fa[MAXN],vis[MAXN*3];
LL all_len;
int f[MAXN][22],g[MAXN][22],h[MAXN][22],dep[MAXN];
bool cmp(edge a,edge b){return a.len<b.len;}
int findfa(int u){return fa[u]==u?u:fa[u]=findfa(fa[u]);}
void krus(){
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++){
int fu=findfa(e[i].u),fv=findfa(e[i].v);
if(fu!=fv){
fa[fu]=fv,vis[i]=1;
E[e[i].u].push_back((Edge){e[i].v,e[i].len});
E[e[i].v].push_back((Edge){e[i].u,e[i].len});
all_len+=e[i].len;
}
}
}
void dfs(int u,int ft,int l){
dep[u]=dep[ft]+1,f[u][0]=ft,g[u][0]=l,h[u][0]=-INF;
for(int i=1;i<=20;i++){
f[u][i]=f[f[u][i-1]][i-1];
g[u][i]=max(g[u][i-1],g[f[u][i-1]][i-1]);
h[u][i]=max(h[u][i-1],h[f[u][i-1]][i-1]);
if(g[u][i-1]>g[f[u][i-1]][i-1]) h[u][i]=max(h[u][i],g[f[u][i-1]][i-1]);
if(g[u][i-1]<g[f[u][i-1]][i-1]) h[u][i]=max(h[u][i],g[u][i-1]);
}
for(int i=0;i<E[u].size();i++){
int v=E[u][i].to;
if(v==ft) continue;
dfs(v,u,E[u][i].len);
}
}
int LCA(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=20;i>=0;i--) if(dep[f[u][i]]>=dep[v]) u=f[u][i];
if(u==v) return v;
for(int i=20;i>=0;i--) if(f[u][i]!=f[v][i]) u=f[u][i],v=f[v][i];
return f[u][0];
}
int ask(int u,int v,int maxx){
int ans=-INF;
for(int i=20;i>=0;i--){
if(dep[f[v][i]]>=dep[u]){
if(maxx!=g[v][i]) ans=max(ans,g[v][i]);
else ans=max(ans,h[v][i]);
v=f[v][i];
}
}
return ans;
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>e[i].u>>e[i].v>>e[i].len;
sort(e+1,e+m+1,cmp);
krus();
dfs(1,0,0);
LL ans=(1ll<<62);
for(int i=1;i<=m;i++){
if(vis[i]) continue;
int lca=LCA(e[i].u,e[i].v);
int max1=ask(lca,e[i].u,e[i].len),max2=ask(lca,e[i].v,e[i].len);
ans=min(ans,all_len-max(max1,max2)+e[i].len);
}
cout<<ans<<endl;
return 0;
}