很多题解对于 ( ext{MST}) 性质的描述,有些奇怪。
以至于:
咳咳,稍微打个岔(
这题确实是个性质题。
我们先考虑只有一组询问的情况。将这组询问中的边按边权排序。对于当前的 (w_i) ,确保 (< w_i) 边权的边已被加入 ( ext{Kruskal}) 的贡献中,再尝试加入当前的 (w_i)。如果询问中有多个与 (w_i) 边权相同的边则全部试图加入,若其中一条无法加入则说明不可行。
这样为什么是对的呢?对于所有的最小生成树,其中每种权值的边的数量是一定的。如果我们加入了 (<w_i) 的所有边(因为这些边中的一些边不加入进最小生成树只会得到更劣解)而使边权与 (w_i) 相同的边无法加入,则说明这条边不在最小生成树上。同样的,如果我们将询问中边权相同的边加入,其中有边无法加入,则说明他们无法同时存在于一棵最小生成树上。
于是我们发现,对于一组询问中,不同边权之间其实并不存在影响。因为对于询问中的任意两条边 (w_i,w_j) ,不妨设 (w_i) 的边权 (<) (w_j) 的边权,那么所有 (<w_j) 边权的边(包括 (w_i))在此前都已被加入进 ( ext{Kruskal}) 的贡献中。
根据上述结论,我们将每组询问拆成若干组边权相同的询问。对于每组边权相同的询问分别处理,然后统计其对每组询问的贡献。具体来说就是将每组边权相同的询问按询问中的边权排序,按只存在一次询问的做法处理这些询问,需要用上一些常规的离线技巧(
值得注意的是,加入一组询问中边权相同的边后需要撤回这组边,因为可能会对后续询问产生影响。这需要我们使用一个支持 撤销上一次操作的并查集:用按秩合并实现能够保留树的结构,用栈记录修改信息,暴力修改,查询,撤销即可。
代码写的非常生草,随便看看就好,嘤嘤嘤(
Show the Code
#include<cstdio>
#include<vector>
#include<algorithm>
int top=0;
std::vector<int> ask[500005];
int x[500005],y[500005],z[500005],fa[500005],size[500005],tmp[500005],res[500005];
struct edge {int x,y,val;} e[500005];
struct state {int x,y,fx,fy;} st[500005];
inline int read() {
register int x=0,f=1;register char s=getchar();
while(s>'9'||s<'0') {if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') {x=x*10+s-'0';s=getchar();}
return x*f;
}
inline void swap(int &x,int &y) {int tmp=y;y=x;x=tmp;}
inline bool cmp1(const edge &x,const edge &y) {return x.val<y.val;}
inline bool cmp2(const int &x,const int &y) {return z[x]<z[y];}
inline bool cmp3(const std::vector<int> &x,const std::vector<int> &y) {return z[x[1]]<z[y[1]];}
inline int find(int x) {while(x!=fa[x]) x=fa[x]; return x;}
inline void merge(int x,int y) {
int fx=find(x),fy=find(y);
if(fx!=fy) {
if(size[fx]>size[fy]) swap(fx,fy);
st[++top]=(state){x,y,fx,fy};
size[fy]+=size[fx]; fa[fx]=fy;
}
}
int main() {
int n=read(),m=read(),num=0,cur=1,Q=0;
for(register int i=1;i<=n;++i) {fa[i]=i;size[i]=1;}
for(register int i=1;i<=m;++i) {
x[i]=read();y[i]=read();z[i]=read();
e[i].x=x[i];e[i].y=y[i];e[i].val=z[i];
}
std::sort(e+1,e+1+m,cmp1); Q=read();
for(register int t=1;t<=Q;++t) {
int len=read(); res[t]=1;
for(register int i=1;i<=len;++i) tmp[i]=read();
std::sort(tmp+1,tmp+1+len,cmp2);
for(register int i=1;i<=len;++i) {
if(z[tmp[i]]!=z[tmp[i-1]]) ask[++num].push_back(t);
ask[num].push_back(tmp[i]);
}
}
std::sort(ask+1,ask+1+num,cmp3);
for(register int i=1;i<=num;++i) {
int flag=1;
while(cur<=m&&e[cur].val<z[ask[i][1]]) {merge(e[cur].x,e[cur].y); ++cur;}
for(register int j=1;j<ask[i].size();++j) {int id=ask[i][j]; if(find(x[id])==find(y[id])) {flag=0;} else {merge(x[id],y[id]);}}
for(register int j=ask[i].size()-1;j>=1;--j) {
int id=ask[i][j]; while(top>0&&st[top].x==x[id]&&st[top].y==y[id]) {fa[st[top].fx]=st[top].fx;size[st[top].fy]-=size[st[top].fx];--top;}
}
res[ask[i][0]]&=flag;
}
for(register int t=1;t<=Q;++t) printf("%s
",res[t]? "YES":"NO");
return 0;
}