CodeForces 827D 淺談最小生成樹性質解析及題目性質分析
世界真的很大
今天考試時做了這道題,當時有點思路但是完全不敢寫
還是要有勇於嘗試的勇氣,寫著寫著可能就寫出來了
不要畏懼於去想,大多數情況下最後的程式碼都比想象的要簡單
對於熟悉的問題要靈活掌握其性質,對於題目要敢於分析題目的獨有性質
但是一定要仔細看題先:
description:
對於一個無向圖的每一條邊,你需要求出一個最大整數權值k,
使得如果把這條邊的權值改為k(而其他邊的權值不變),
整個圖的任意一個最小生成樹上都有這條邊
input
第一行兩個整數n,m
接下來m行每行3個整數u,v,w,表示u,v之間有一條邊權為w的邊
output
m行,每行一個整數,表示第i 條邊的最大邊權
一下午的思考後,思路什麼的還是比較明確了,先總結如下
首先題目很明確的要求了最小生成樹,那麼我們不管三七二十一,先求一顆最小生成樹再說
那麼就得到了兩類邊,一類是在最小生成樹上的邊,一類是不在最小生成樹上的邊
對於前者,我們想要知道可以使其一直留在最小生成樹上的最大邊權,對於後者,我們想要知道可以使其躋身最小生成樹的最大邊權
先考慮後者
對於一條不在最小生成樹上的邊,要想讓它到最小生成樹上,而樹上兩點之間的路徑是唯一的,這條邊的左右兩點就會連通,就意味著之前最小生成樹上使得這兩條邊連通的路徑上的一條邊會被擠下去,那麼那條邊會被擠下去呢,當然是裡面邊權最大的那條邊了,
所以只要這條現在不在最小生成樹上的邊的權值變得比那條路徑上權值最大的邊權值小1,就能把他擠下去,而兩點之間又必須連通,另一條路上被擠下去了一條,斷了,所以這條邊就斷不開了,一定會出現在任意一個最小生成樹上
也就是說,對於一條不在最小生成樹上的邊,其最大的權值就是最小生成樹上使得這條邊兩端點連通的路徑上邊權最大的那條邊的權值減一
對於前者,也就是在最小生成樹上的一條邊,考慮要想使得其從最小生成樹上被擠下去,首先就是要使得由於這條邊斷開,左右兩端點通過新的路徑重新連通
而由於重新連通是需要新的邊的而只所以這條邊會被替代,只可能因為原本的邊的邊權比新的邊的邊權大,才會被替代
也就是說,要想這條邊永遠留在最小生成樹上,就需要其值比所有能使得其左右兩端在其斷開後重新連通的邊的值都要小,就是這些邊的最小值減一
對於第一種情況,換句話說就是在最小生成樹上求一條鏈的最值,可以用倍增來解決
對於第二種情況,列舉所有在最小生成樹上的邊,再去找所有能使得其左右兩點重新連通的不在最小生成樹上的邊顯然不現實,換個思路考慮,每條不在不在最小生成樹上的邊可以更新答案的範圍,就是其左右兩點在最小生成樹上的一條鏈,相當於每次更新就是對於一條鏈上的答案全部對其取個min值,而這是樹上的路徑維護問題,顯然可以用樹鏈剖分的方法來做
現在已經是這道題的正解了,複雜度nlogn,但還不夠
樹鏈剖分在套個線段樹,寫起來。。。
最小生成樹的性質幾乎已經分析夠了,但是對於這道題目單獨而言,還有更有效率的解法
考慮第一種情況,我們是列舉所有不在最小生成樹上的邊,而第二種情況,本來是列舉所有在最小生成樹上的邊,轉化之後也變成了列舉所有不在最小生成樹上的邊了,好像可以一起搞定
考慮第一次查詢的範圍,是列舉的邊左右兩點在最小生成樹上的一條鏈,而第二次更新的範圍也是左右兩點的一條鏈
範圍的相同更加印證了兩種情況可以一起來討論了
現在對於每次更新的範圍和查詢範圍相同,依然可以用樹鏈剖分來搞定
現在考慮,查詢自不用說,複雜度非常優美,考慮更新,有沒有比起樹鏈剖分更好寫,但是同樣高效的方法
查詢是用的倍增,顯然更新沒辦法像倍增一樣跳著跳著地更新,所以必然是其他方法
考慮每次更新是取的min值,就是說,如果一條鏈已經被一條邊更新了,再被另一條邊權較大的邊更新,是沒有用的。這就提醒我們,已經被邊權小的邊更新過答案的區域,是不會再被更大的邊更新的,在考慮每次更新是獨立計算答案,最後需要的只是所有不在最小生成樹上的邊更新後疊加的值,對於更新的順序完全不關心
那麼我們就可以自定義更新順序了,選擇按照邊權排序來更新就行
這樣之前更新的區域就不用重複更新了,可以用類似並查集或者連結串列的方法跳著跳著更新還沒有更新的區域
這樣由於每個點只會被更新一次,複雜度就是均攤O(m)的了
不僅非常優秀而且意外好寫
完整程式碼:
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
const int INF=0x3f3f3f3f;
struct edge
{
int v,u,last,w,id;
}ed[400010],e[400010];
int n,m,tot=0,num=0,head[200010],anc[200010][20],st[200010][20];
int dep[200010],ans[200010],to[200010],fa[200010],used[200010];
bool cmp(const edge &a,const edge &b)
{
return a.w<b.w;
}
int getfather(int x)
{
if(x==fa[x]) return x;
return fa[x]=getfather(fa[x]);
}
void add(int u,int v,int w,int id)
{
num++;
ed[num].v=v;
ed[num].id=id;
ed[num].w=w;
ed[num].last=head[u];
head[u]=num;
}
void dfs(int u,int f)
{
anc[u][0]=f;
for(int i=1;i<=18;i++)
{
anc[u][i]=anc[anc[u][i-1]][i-1];
st[u][i]=max(st[u][i-1],st[anc[u][i-1]][i-1]);
}
for(int i=head[u];i;i=ed[i].last)
{
int v=ed[i].v;
if(v==f) continue ;
to[v]=ed[i].id;
st[v][0]=ed[i].w;
dep[v]=dep[u]+1;
dfs(v,u);
}
}
int lca(int u,int v,int &d)
{
d=0;
if(dep[u]<dep[v]) swap(u,v);
for(int i=18;i>=0;i--)
if(dep[anc[u][i]]>=dep[v])
{
d=max(d,st[u][i]);
u=anc[u][i];
}
if(u==v) return u;
for(int i=18;i>=0;i--)
if(anc[u][i]!=anc[v][i])
{
d=max(d,st[u][i]),d=max(d,st[v][i]);
u=anc[u][i],v=anc[v][i];
}
d=max(d,st[u][0]),d=max(d,st[v][0]);
return anc[u][0];
}
void Kruscal()
{
sort(e+1,e+m+1,cmp);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
int u=e[i].u,v=e[i].v,w=e[i].w;
int x=getfather(u),y=getfather(v);
if(x!=y)
{
fa[x]=y,used[i]=1;
add(u,v,w,e[i].id);
add(v,u,w,e[i].id);
tot++;
}
if(tot==n-1) return ;
}
}
void modify(int u,int v,int d)
{
u=getfather(u);
while(dep[u]>dep[v])
{
ans[to[u]]=min(ans[to[u]],d);
int y=getfather(anc[u][0]);
fa[u]=y;
u=getfather(u);
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w),e[i].id=i;
Kruscal();
dfs(1,1);
memset(ans,INF,sizeof(ans));
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++)
{
if(used[i]) continue ;
int u=e[i].u,v=e[i].v,f=lca(u,v,ans[e[i].id]);
ans[e[i].id]--;
modify(u,f,e[i].w-1);
modify(v,f,e[i].w-1);
}
for(int i=1;i<=m;i++) if(ans[i]==INF) printf("-1\n");
else printf("%d\n",ans[i]);
return 0;
}
/*
EL PSY CONGROO
*/
嗯,就是這樣