先不考虑数据结构部分,尝试猜一下结论。
结论:一个连通块有解当且仅当连通块的度数为偶数。
然后这题要你最大边权最小。最无脑的方法就是直接上
lct
\text{lct}
lct 。真省事啊
我第一眼想到的还是整体二分。这玩意非常好写。
但是为什么也可以用线段树分治来做呢。这需要简单的分析一下性质。考虑求出每条边在哪些生成树上出现过 ,分析可知这一定是一段区间。
然后是非常玄学的操作。考虑离线,然后 倒着处理询问 (真是违背常理啊),这样的话一条边如果原来在生成树上,那么删去一条边后显然还是在生成树上(上面的分析告诉你的),这意味着恰好有一条边被加了进去,而这条边被加进去的条件就是连接的两个点不联通。
基于上述分析,我们可以尝试分治。首先,计算出影响区间的右端点在
[
m
i
d
+
1
,
r
]
[mid+1,r]
[ mi d + 1 , r ] 之间的边,那么我们就要先算出哪些边的影响区间
≥
m
i
d
+
1
\ge mid+1
≥ mi d + 1 ,哪些边的影响区间
≤
m
i
d
\le mid
≤ mi d ,这样就将边集分成了两部分。使用可撤销并查集,然后往两边递归即可。怎么计算答案呢,发现递归到叶子节点的时候可以把那颗生成树算出来,然后就做完了。
事实上算边的影响区间部分我们可以不用真的将边分到两个集合中去。考虑更聪明的想法,倒着往前处理询问的时候,之前在生成树上的边还是在生成树上,那么我们事实上只需要在此基础上加入边权更大的边(当然这条边必须合法),直到得到的图满足条件。那么我们就知道新加入的这些边的影响区间的右端点就是当前这个位置,在线段树上对应部分打一个标记即可。这个半在线做法非常神奇。
这高级玩意估计考场上也想不到/kk
复杂度
O
(
n
log
n
log
m
)
O(n\log n\log m)
O ( n log n log m ) 。
实现了后一种较为复杂的方法。
该死,有一个地方打挂了。
#include <bits/stdc++.h>
#define ll long long
#define fi first
#define se second
#define pb push_back
#define inf 0x3f3f3f3f
#define db double
#define cpx complex<db>
using namespace std;
const int N=3e5 +5 ;
int n,m,cnt,res[N],now;
int fa[N],s[N],sa[N];
vector<int >G[N<<2 ];
struct node {
int x,y,z;
}e[N];
bool cmp (int x,int y) {
return e[x].z<e[y].z;
}
int find (int x) {
return fa[x]==x?x:find (fa[x]);
}
void modify (int p,int l,int r,int ql,int qr,int x) {
if (ql>qr)return ;
if (ql<=l&&r<=qr){
G[p].pb (x);
return ;
}
int mid=l+r>>1 ;
if (ql<=mid)modify (p<<1 ,l,mid,ql,qr,x);
if (mid<qr)modify (p<<1 |1 ,mid+1 ,r,ql,qr,x);
}
void solve (int p,int l,int r) {
vector<pair<int ,int >>bak;
for (auto pos:G[p]){
int x=find (e[pos].x),y=find (e[pos].y);
if (x!=y){
cnt-=(s[x]&1 )+(s[y]&1 );
if (s[x]>s[y])swap (x,y);
bak.pb (make_pair (x,y));
fa[x]=y,s[y]+=s[x],cnt+=(s[y]&1 );
}
}
if (l==r){
while (cnt&&now<=m){
int x=find (e[sa[now]].x),y=find (e[sa[now]].y);
if (sa[now]<l)modify (1 ,1 ,m,sa[now],l-1 ,sa[now]);
if (sa[now]<=l&&x!=y){
cnt-=(s[x]&1 )+(s[y]&1 );
if (s[x]>s[y])swap (x,y);
bak.pb (make_pair (x,y));
fa[x]=y,s[y]+=s[x],cnt+=(s[y]&1 );
}
now++;
}
if (!cnt)res[l]=e[sa[now-1 ]].z;
else res[l]=-1 ;
}
else {
int mid=l+r>>1 ;
solve (p<<1 |1 ,mid+1 ,r);
solve (p<<1 ,l,mid);
}
reverse (bak.begin (),bak.end ());
for (auto x:bak){
cnt-=s[x.se]&1 ;
fa[x.fi]=x.fi,s[x.se]-=s[x.fi];
cnt+=(s[x.fi]&1 )+(s[x.se]&1 );
}
}
int main () {
ios::sync_with_stdio (false );
cin.tie (0 ),cout.tie (0 );
cin>>n>>m;for (int i=1 ;i<=n;i++)fa[i]=i,s[i]=1 ;
for (int i=1 ;i<=m;i++){
cin>>e[i].x>>e[i].y>>e[i].z,sa[i]=i;
}
sort (sa+1 ,sa+1 +m,cmp);
cnt=n,now=1 ;
solve (1 ,1 ,m);
for (int i=1 ;i<=m;i++)cout<<res[i]<<"\n" ;
}
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」