当前位置 博文首页 > 文章内容

    题解「CF891C Envy」

    作者: 栏目:未分类 时间:2020-09-30 9:00:31

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    很多题解对于 \(\text{MST}\) 性质的描述,有些奇怪。

    以至于:

    咳咳,稍微打个岔(

    这题确实是个性质题
    我们先考虑只有一组询问的情况。将这组询问中的边按边权排序。对于当前的 \(w_i\) ,确保 \(< w_i\) 边权的边已被加入 \(\text{Kruskal}\) 的贡献中,再尝试加入当前的 \(w_i\)。如果询问中有多个与 \(w_i\) 边权相同的边则全部试图加入,若其中一条无法加入则说明不可行。

    这样为什么是对的呢?对于所有的最小生成树,其中每种权值的边的数量是一定的。如果我们加入了 \(<w_i\) 的所有边(因为这些边中的一些边不加入进最小生成树只会得到更劣解)而使边权与 \(w_i\) 相同的边无法加入,则说明这条边不在最小生成树上。同样的,如果我们将询问中边权相同的边加入,其中有边无法加入,则说明他们无法同时存在于一棵最小生成树上。

    于是我们发现,对于一组询问中,不同边权之间其实并不存在影响。因为对于询问中的任意两条边 \(w_i,w_j\) ,不妨设 \(w_i\) 的边权 \(<\) \(w_j\) 的边权,那么所有 \(<w_j\) 边权的边(包括 \(w_i\))在此前都已被加入进 \(\text{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\n",res[t]? "YES":"NO");
    	return 0;
    }