CF609E:Minimum spanning tree for each edge題解
阿新 • • 發佈:2021-11-30
題目連結:CF:Minimum spanning tree for each edge
題解:他要求我們求包含第i條邊的樹的最小值,我們可以先跑一遍最小生成樹,把最小生成樹求出來,然後再進行加邊刪邊處理。
當我們求出一個最小生成樹後,假如再連邊後肯定會形成一個自環,我們考慮在這個自環上刪邊(不是真刪,而是輸出減去其權值後的 ans )。
比如在該圖中,我們連線4,5兩點,這三點會與其 LCA 成頂點形成自環 ,黃色那條邊肯定是不能動的,要動的就是4-2和5-2這兩條邊,我們在處理 LCA 時變換一下,改為求解其從x點到其 LCA 的所有連邊上的最大值。我們考慮樹上倍增,定義一陣列 walth[i][j] 為從第i點開始向上跳 2^j 個點後第i點與第 2^j 點的連邊中的最大值,然後用類似於求 LCA 的做法進行處理最終求得一條鏈邊上的 max 值。
具體實現如下:
#include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #define Maxn 200005 using namespace std; typedef long long ll; //此部分用於樹上倍增 ll head[Maxn*2],lg[Maxn*4]; struct acb{ ll next,to,w; }tree[Maxn*2]; ll cut; void Add(ll u,ll v,ll w){ tree[++cut].to=v; tree[cut].next=head[u]; tree[cut].w=w; head[u]=cut; } // ll anc[Maxn][30],dept[Maxn],walth[Maxn][30]; ll n,m,fa[Maxn],top,len; bool bin[Maxn]; struct abc{ ll u,v,w,ty,id; }edge[Maxn*2]; bool cmp(abc x,abc y){ return x.w<y.w; } bool rbq(abc x,abc y){ return x.id<y.id; } ll Find(ll x){ while(x!=fa[x]) x=fa[x]=fa[fa[x]]; return x; } void add(ll u,ll v,ll w){ edge[++top].v=v; edge[top].w=w; edge[top].u=u; edge[top].ty=top; } ll kruskal(){//處理出一個最小生成樹 sort(edge+1,edge+m+1,cmp); ll cnt=0,res=0; for(ll i=1;i<=m;i++){ ll u=edge[i].u,v=edge[i].v; ll rootx=Find(u),rooty=Find(v); if(rootx==rooty)continue; bin[edge[i].ty]=true;//記錄這一條邊在生成樹裡面了,假如要加的邊也在生成樹其中,那就不用處理 Add(u,v,edge[i].w);//另外將最小生成樹上的每一條邊存入鏈式前向星中,以備後面的LCA Add(v,u,edge[i].w); res+=edge[i].w; fa[rooty]=rootx; // printf("%d %d\n",u,v); if(++cnt==n-1)break; } return res; } //以上為最小生成樹部分 void dfs(ll u,ll fath,ll w) { dept[u]=dept[fath]+1; anc[u][0]=fath; walth[u][0]=w; for(int i=1;(1<<i)<=dept[u];i++){ anc[u][i]=anc[anc[u][i-1]][i-1]; walth[u][i]=max(walth[u][i-1],walth[anc[u][i-1]][i-1]);//求解從u點出發直到第2^i個點之間的邊的權值的最大值 } for(int i=head[u];i;i=tree[i].next){ if(fath!=tree[i].to){ dfs(tree[i].to,u,tree[i].w); } } } ll LCA(ll x,ll y){ if(dept[x]<dept[y]) swap(x,y); ll maxx=-1,maxy=-1; while(dept[x]>dept[y]){ maxx=max(maxx,walth[x][lg[dept[x]-dept[y]]-1]);//萬一兩點重合了,我們就只需要一條邊上的最大值 //哪怕它們沒重合,我們仍然需要這一點在往上跳的途中的最大值 x=anc[x][lg[dept[x]-dept[y]]-1];//注:順序不要搞反了 } if(x==y) return maxx; for(int i=lg[dept[x]]-1;i>=0;i--){ if(anc[x][i]!=anc[y][i]){ maxx=max(maxx,walth[x][i]); //兩點沒有重合,那就需要我們兩邊一起求取最大值 maxy=max(maxy,walth[y][i]); x=anc[x][i]; y=anc[y][i]; } } maxx=max(walth[x][0],maxx);//注意,我們最後求到的x值是LCA的第一級兒子,還有一條邊(LCA與其第一級兒子的連邊)沒算 maxy=max(walth[y][0],maxy); return max(maxx,maxy); } bool check(ll x){ if(bin[x]) return true; else return false; } ll work(ll x){ ll res=len; if(check(x))return res;//假如這一條邊在最小生成樹中,那麼我們就不處理 else res+=edge[x].w; //否則,我們預先加上目標的那一條邊的權值 ll u=edge[x].u; ll v=edge[x].v; ll lens=LCA(u,v); return res-lens; } //以上為程式碼實踐 int main(){ scanf("%lld%lld",&n,&m); for(ll i=1;i<=n;i++) fa[i]=i; for(ll i=1;i<=m;i++){ ll u,v,w; edge[i].id=i; scanf("%lld%lld%lld",&u,&v,&w); add(u,v,w); } len=kruskal(); sort(edge+1,edge+m+1,rbq);//注:在求完最小生成樹後一定要把陣列糾正回來,因為題目要的是原裝邊加上並處理後的值 lg[1]=1; for(int i=2;i<=Maxn;i++) lg[i]=lg[i-1]+((1<<lg[i-1])==i); dfs(1,0,0); for(ll i=1;i<=m;i++){ ll ans=work(i); printf("%lld\n",ans); } return 0; }