考虑先求出一棵最小生成树,边权之和为$sum$.
考虑加入一条非树边$(u,v,w)$.显然用它替换掉树上$u\rightarrow v$路径中的某一条边,得到的新生成树权值和必然不小于$sum$(否则与最小生成树的定义违背).因此替换多条边是不优的.
显然用这条边替换掉边权尽量大的边(但要边权之和必须小于$sum$) 更优.记$u\rightarrow v$路径上的最大边权是$mx$,严格次大边权是$sec$.若$mx<w$,则可以替换掉$mx$,新生成树权值和为$sum-mx+w$.否则$mx=w$(注意不会有非树边满足$w<mx$,否则替换会得到更小的最小生成树,与定义违背),只能替换$sec$,新生成树权值和为$sum-sec+w$
需要一个数据结构支持查询链上最大边权,次大边权的操作.树剖+线段树/ST表,LCT均可.这里我采用了LCT,时间复杂度为$\mathcal O(m\log n)$,空间线性.
由于LCT常数较大而本题时限较短,需要卡常.我卡了50min,你呢
若使用LCT,有个细节是LCT无法直接维护边权,可以用边权转点权的技巧常数更大了
/**********/
#define MAXN 300011
struct DSU//并查集,用于求最小生成树
{
ll fa[MAXN];
void build(ll n){for(ll i=1;i<=n;++i)fa[i]=i;}
ll find(ll x)
{
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
bool uni(ll u,ll v)
{
u=find(u),v=find(v);
if(u==v)return 0;
fa[u]=v;return 1;
}
}s;
struct edge
{
ll u,v,w;
bool operator <(const edge& t)const{return w<t.w;}
}e[MAXN];
std::vector<ll>g[MAXN];
bool used[MAXN];
ll Kru(ll n,ll m)
{
std::sort(e+1,e+m+1);
s.build(n);
ll ans=0;
for(ll i=1;i<=m;++i)
if(s.uni(e[i].u,e[i].v))
{
ans+=e[i].w;
g[e[i].u].push_back(e[i].v),g[e[i].v].push_back(e[i].u);
used[i]=1;
}
return ans;
}
struct LCT//LCT,维护链上最大边权和严格次大边权
{
ll fa[MAXN],son[MAXN][2],val[MAXN], maxv[MAXN],sec[MAXN],tag[MAXN], tot;
void init(ll n){tot=n;}
void pushup(ll x)//卡常过的pushup
{
ll l=son[x][0],r=son[x][1];
maxv[x]=max(max(maxv[l],maxv[r]),val[x]);
sec[x]=0;
if(maxv[l]<maxv[x])umax(sec[x],maxv[l]);
else umax(sec[x],sec[l]);
if(val[x]<maxv[x])umax(sec[x],val[x]);
if(maxv[r]<maxv[x])umax(sec[x],maxv[r]);
else umax(sec[x],sec[r]);
}
void pushdown(ll x)
{
if(tag[x])
{
std::swap(son[x][0],son[x][1]);
tag[son[x][0]]^=1,tag[son[x][1]]^=1;
tag[x]=0;
}
}
bool not_root(ll x){return son[fa[x]][0]==x||son[fa[x]][1]==x;}
void rotate(ll x)
{
ll y=fa[x],z=fa[y],k=(son[y][1]==x);
if(not_root(y))son[z][son[z][1]==y]=x;
fa[x]=z;
son[y][k]=son[x][!k],fa[son[x][!k]]=y;
son[x][!k]=y,fa[y]=x;
pushup(y);//再pushu(x)就会TLE
}
ll s[MAXN];
void splay(ll x)
{
ll top=0;
s[++top]=x;
for(ll y=x;not_root(y);y=fa[y])s[++top]=fa[y];
while(top)pushdown(s[top--]);
while (not_root(x))
{
ll y=fa[x];
if(not_root(y))rotate((son[y][1]==x)==(son[fa[y]][1]==y)?y:x);
rotate(x);
}
pushup(x);
}
void access(ll x)
{
for(ll y=0;x;y=x,x=fa[x])
splay(x),son[x][1]=y;
}
void make_root(ll x){access(x),splay(x),tag[x]^=1;}
void link(ll x,ll y)
{
make_root(x);
fa[x]=y;
}
void merge(ll x,ll y,ll v)
{
val[++tot]=v;
link(x,tot),link(y,tot);
}
pll split(ll x,ll y)//求链上最大边权和严格次大边权
{
make_root(x),access(y),splay(y);
return pll(maxv[y],sec[y]);
}
}lct;
int main()
{
ll n=read(),m=read();
for(ll i=1;i<=m;++i)e[i].u=read(),e[i].v=read(),e[i].w=read();
ll sum=Kru(n,m);
lct.init(n);
for(ll i=1;i<=m;++i)
if(used[i])lct.merge(e[i].u,e[i].v,e[i].w);
ll ans=INF;
for(ll i=1;i<=m;++i)
{
if(used[i])continue;
pll mx=lct.split(e[i].u,e[i].v);
if(mx.first<e[i].w)umin(ans,sum-mx.first+e[i].w);
else umin(ans,sum-mx.second+e[i].w);
}
printf("%lld",ans);
return 0;
}
流弊
大佬,我怎末感觉d1和d2数组,存的是路径上一次跳过的1条或多条边的路径呢???而不是路径上的最大边权呢
我也有这个疑问, 请问您明白咋回事了嘛? 谢谢!
倍增就完事了吧。。。不会lct啊
卧槽,终于看到您的题解里面有一个我会的了。。您太强了Orz
初二就会LCT,Orz
您初一就会了吧。。