UOJ61. 【UR #5】怎樣更有力氣
Statement
給定一棵 \(n\) 點樹 \(T\) 和 \(m\) 個操作 v u w
:
- 在 \(T\) 中 \(u,v\) 的最短路上所有點裡面選出若干對(可以不選,可以重複),在每一對之間加邊,花費為 \(w\) .
- 有 \(p\) 個限制
t a b
,表示第 \(t\) 次不能在 \(a,b\) 之間加邊,保證 \(a,b\) 在 \(u,v\) 的路徑上且無重複。
求所有加的邊形成 \(n\) 點連通圖的最小代價。 \(n,m,p\leq 3e5\) .
Solution
一開始看錯題了
一個顯然的想法是,把每一天按照 \(w\) 升序,然後每次儘可能多地合併連通塊。
有一個顯然的性質: 對於第 \(i\) 天,設當天有 \(num\) 個限制,如果 \(dis(u,v)>num\) ,那麼這一條路徑上的所有點之間都能連通,且必須連通
Proof
長度是 \(dis(u,v)\) ,點數就是 \(dis(u,v)+1\) .既然 \(dis(u,v)\) 比 \(num\) 大,那麼就算一個點包攬了 \(num\) 個限制,也至少能向一個點連邊,這樣就一定可以使得路徑上所有點連通。而由於我們是將每一次操作按 \(w\) 升序的,所以這一次將它連邊一定不會比後面再做劣,因此這種情況下,第 \(i\) 次做完之後一定是 \(path(u,v)\) 全部連通。
那麼對於滿足這個性質的情況,直接連就好了,反正連完就是同一個連通塊。(網上題解為啥說的是能直接連邊啊……雖然是等價的)
現在來考慮 \(dis(u,v)\leq num\) 的情況。
首先,對於 \(t\) 的所有限制,建立一個子圖 \(g\) ,並暴力把 \(u\to v\) 路徑上的所有點都存下來,設為 \(path[]\) .令 \(t\) 的限制個數為 \(k\) .
然後,選取一個度數最小的點 \(x\) ,將路徑上的點集分成兩類:\(V_1\) 中的點在限制子圖中和 \(x\) 相鄰,\(V_2\) 反之。
顯然,對於 \(V_2\) 的點,直接和 \(x\) 連邊就好了。
對於 \(V_1\)
否則,暴力列舉 \(V_1\) 中的點,嘗試兩兩連邊。由於度數最小所以 \(deg[x]\) 是根號級別的,複雜度 \(\mathcal{O}(k)\) .
所以總體複雜度是 \(\mathcal{O}(n\log n)\) (同級複雜度),瓶頸在於排序。
Code
//Author: RingweEH
const int N=3e5+10;
struct Date
{
int u,v,w,t;
bool operator < ( const Date &tmp ) const { return w<tmp.w; }
}a[N];
struct DSU
{
int fa[N];
int find( int x ) { return x==fa[x] ? x : fa[x]=find(fa[x]); }
DSU() { for ( int i=1; i<=N-10; i++ ) fa[i]=i; }
}D1,D2;
int n,m,k,fa[N],dep[N];
ll ans=0;
bool vis[N];
vector<Date> restr[N];
vector<int> g[N];
stack<Date> sta;
bool Check_Length( int u,int v,int num ) //判斷dis(u,v)的長度和限制個數的大小關係
{
while ( num-- )
{
if ( dep[u]>dep[v] ) swap( u,v );
v=fa[v];
if ( u==v ) return 0;
}
return 1;
}
void Merge( int u,int v,int w )
{
u=D2.find( u ); v=D2.find( v );
if ( u!=v ) D2.fa[v]=u,ans+=w;
}
void Add( int u,int v )
{
g[u].push_back( v ); g[v].push_back( u );
Date tmp; tmp.u=u; tmp.v=v; tmp.w=tmp.t=0; sta.push( tmp );
}
void Clear( int u=0,int v=0 )
{
while ( !sta.empty() )
{
u=sta.top().u; v=sta.top().v; sta.pop();
g[u].clear(); g[v].clear();
}
}
int path[N],lenth;
void Get_Path( int u,int v )
{
lenth=0; bool fl=0;
do
{
fl=(u==v);
if ( dep[u]>dep[v] ) swap( u,v );
path[++lenth]=v; v=fa[v];
} while ( !fl );
}
int main()
{
n=read(); m=read(); k=read();
for ( int i=2; i<=n; i++ )
fa[i]=read(),dep[i]=dep[fa[i]]+1;
for ( int i=1; i<=m; i++ )
a[i].u=read(),a[i].v=read(),a[i].w=read(),a[i].t=i;
for ( int i=1,u,v,t; i<=k; i++ )
t=read(),u=read(),v=read(),restr[t].push_back( (Date){u,v,0,0} );
sort( a+1,a+1+m );
for ( int i=1; i<=m; i++ )
{
int u=a[i].u,v=a[i].v,w=a[i].w,t=a[i].t;
if ( Check_Length(u,v,restr[t].size()) ) //滿足性質
{
u=D1.find( u ); v=D1.find( v );
while ( u^v )
{
if ( dep[u]>dep[v] ) swap( u,v );
Merge( v,fa[v],w ); D1.fa[v]=fa[v]; v=D1.find( v );
}
}
else
{
for ( Date j : restr[t] )
Add( j.u,j.v ); //對於限制建子圖
Get_Path( u,v ); int mn=path[1];
for ( int j=2; j<=lenth; j++ ) //找度數最小的點
if ( g[path[j]].size()<g[mn].size() ) mn=path[j];
for ( int j : g[mn] ) //標記相鄰點
vis[j]=1;
for ( int j=1; j<=lenth; j++ ) //x & V2
if ( !vis[path[j]] ) Merge( mn,path[j],w );
for ( int j : g[mn] )
{
int sum=0;
for ( int p : g[j] )
sum+=vis[p];
if ( g[j].size()-sum-1<lenth-g[mn].size()-1 ) Merge( j,mn,w );
//deg[y]<|V2|
}
for ( int j : g[mn] )
vis[j]=0;
for ( int j : g[mn] )
{
for ( int p : g[j] )
vis[p]=1;
for ( int p : g[mn] ) //V1中兩兩嘗試連邊
if ( !vis[p] ) Merge( j,p,w );
for ( int p : g[j] )
vis[p]=0;
}
Clear();
}
}
printf( "%lld\n",ans );
return 0;
}