题解[CF891C Envy]
题意:给定一张无向连通图,每次给出一些边,询问这些边能否同时在一个最小生成树中出现。
先建出任意一个最小生成树,对树上与非树上的边讨论。
首先,如果询问的边会构成一个环,那就完全不可能在同一最小生成树中。
其次,考虑一条非树边 \((x,y,w)\) 什么时候能替换一条在原先最小生成树上的边。
那就必须满足 \(x,y\) 两个点在原先最小生成树路径上的边的最大值 \(\geq\) \(w\) ,不然不可行。
那这时候这条边就能将这条边权最大的边替换掉。
但如果这条边权最大的边被要求了必须出现,就需要另外的边去替换。
由于有动态的替换与求路径最大值,可以考虑 \(\text{LCT}\) 化边为点实现。
先将询问中在原最小生成树上的边的权值设为 \(0\) 保证不会被替换。
再对非树边查路径最大值看能否替换,并记录下被替换过的边,在最终要复原回去。
这样的时间复杂度是 \(O(n\log n)\) 的(默认 \(n,m\) 同阶)。
但有于 \(\text{LCT}\) 的巨大常数,且为了查路径最值的化边为点,点数已去到 \(1e6\) 级别。
所以这种方法特别卡常,顺手最劣解。
最终代码:
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m,q,x,y,v,k,ax,ay,tot;bool flag,bb[N];
int w[N],ww[N],xx[N],yy[N],a[N],f[N],id[N];
inline int getf(int x){while(x^f[x])x=f[x]=f[f[x]];return x;}
struct pre_state{int x,y;}ps[N<<1];
namespace FastIO
{//此处省略带缓存的 FastIO
}
using namespace FastIO;
struct lct{
int mxid[N],son[N][2],tmpid,anc[N],rev[N],i,y,_y,_x,_id,rx,ry,mxy;bool b;
inline bool nroot(int x){return son[anc[x]][0]==x||son[anc[x]][1]==x;}
inline bool p(int x){return son[anc[x]][1]==x;}
inline void rev_(int x){rev[x]^=1;son[x][0]^=son[x][1]^=son[x][0]^=son[x][1];}
inline void fix(int x){
mxid[x]=x;
if(w[tmpid=mxid[son[x][0]]]>w[x])mxid[x]=tmpid;
if(w[tmpid=mxid[son[x][1]]]>w[mxid[x]])mxid[x]=tmpid;
}
inline void pushdown(int x){
if(rev[x]){if(son[x][0])rev_(son[x][0]);if(son[x][1])rev_(son[x][1]);rev[x]=0;}
}
void pushall(int x){if(nroot(x))pushall(anc[x]);pushdown(x);}
inline void rotate(int x){
y=anc[x];_x=anc[y];b=p(x);
if(nroot(y))son[_x][p(y)]=x;
anc[x]=_x;
anc[son[y][b]=son[x][!b]]=y;
anc[son[x][!b]=y]=x;
fix(y);fix(x);fix(_x);
}
inline void splay(int x){
pushall(x);for(;i=anc[x],nroot(x);rotate(x))if(nroot(i))rotate(p(x)==p(i)?i:x);
}
inline void access(int x){
for(_y=0;x;_y=x,x=anc[x])splay(x),son[x][1]=_y,fix(x);
}
inline void makeroot(int x){access(x);splay(x);rev_(x);}
inline void split(int x,int y){makeroot(x);access(y);splay(y);}
inline void link(int x,int y){makeroot(x);anc[x]=y;}
inline void cut(int x,int y){split(x,y);son[y][0]=anc[x]=0;fix(y);}
inline void link(int x,int y,int id){
split(x,y);mxy=mxid[y];
if(w[mxy]>=w[id]){
_id=mxy;
cut(xx[_id],_id);cut(_id,yy[_id]);
link(x,id);link(id,y);
ps[++tot]=(pre_state){_id,id};
}
else flag=1;
}
inline void recover(){
for(;tot;--tot){
ry=ps[tot].y,rx=ps[tot].x;
cut(xx[ry],ry);cut(yy[ry],ry);
link(xx[rx],rx);link(yy[rx],rx);
}
}
void getbb(){for(int i=1;i<=m;++i)split(xx[i],i),bb[i]=son[i][0]==xx[i];}
}L;
bool cmp(int a,int b){return w[a]<w[b];}
main(){
qin>>n>>m;register int i;
for(i=1;i<=m;++i)qin>>xx[i]>>yy[i]>>w[i];
for(i=1;i<=m;++i)xx[i]+=m,yy[i]+=m,ww[i]=w[i],id[i]=i;
for(i=1;i<=n;++i)f[i]=i;
sort(id+1,id+m+1,cmp);
for(i=1;i<=m;++i){
x=xx[id[i]]-m,y=yy[id[i]]-m;
ax=getf(x),ay=getf(y);
if(ax==ay)continue;
f[ax]=ay;
L.link(x+m,id[i]);L.link(y+m,id[i]);
}//老老实实打kruskal
L.getbb();tot=0;qin>>q;
for(i=1;i<=n;++i)f[i]=i;
while(q--){
qin>>k;flag=1;
for(i=1;i<=k;++i)qin>>a[i],flag&=bb[a[i]];
if(flag){puts("YES");continue;}//全部在原MST的边显然可行
flag=0;
for(i=1;i<=k;++i){
ax=getf(xx[a[i]]-m);
ay=getf(yy[a[i]]-m);
if(ax==ay)flag=1;
else f[ax]=ay;
}//并查集判环
for(i=1;i<=k;++i)x=xx[a[i]]-m,f[x]=x,y=yy[a[i]]-m,f[y]=y;
if(flag){puts("NO");continue;}
for(i=1;i<=k;++i)if(bb[a[i]])w[a[i]]=0,L.splay(a[i]);
for(i=1;i<=k;++i)if(!bb[a[i]]){
L.link(xx[a[i]],yy[a[i]],a[i]);
if(flag)break;
}
for(i=1;i<=k;++i)if(bb[a[i]])w[a[i]]=ww[a[i]],L.splay(a[i]);
L.recover();//复原
if(flag)puts("NO");
else puts("YES");
}
}