• CF891C Envy


    关于同一张图上的最小生成树,有一些性质:

    对于任意权值的边,所有最小生成树中这个权值的边的数量是一定的
    对于任意正确加边方案,加完小于某权值的所有边后图的连通性是一样的
    据以上性质,判断某些边是否能够在同一最小生成树中同时出现时,不同权值的边之间不互相影响。

    所以我们可以对于每一条边用Kruskal预处理出加完小于这条边的权值后的所有边后,这条边的两个端点所在的连通块,然后对于每个询问,每种权值分开考虑,看这些边连接的连通块是否构成环(注意不要漏掉自环),这里可以直接并查集

     ============================================================


    可得以下性质
    1:对于某个图,它可能会有多种最小生成树的方案,但不管在哪种方案中,某种权值的边数的值是固定的,至于具体是取同权值中的哪几条边是无所谓的。
    2:如果将所有小于等于x的边做最小生成树,则合法方案的连通性是一样的,即能连通的自然会连通,不能连通的也始终不能连通。

    对于一组询问,对于所有权值,权值为x的有k个,那么可以事先将<x的边全部加入,然后将这k个边加入,看看能不能全部加入进去。如果有一个成环了,那么肯定是不行的。

    那么q组询问,可以离线下来,对于(边权,询问编号)二元组排序,然后对于同一边权的同一组询问,尝试加入,到了下一组询问就撤销,然后处理完之后又全部加入进去。用可撤销并查集就可以做了。
    例如对于下面这组数据
    5 7
    1 2 2
    1 3 2
    2 3 1
    2 4 1
    3 4 1
    3 5 1
    4 5 2
    2
    4 3 4 5 6
    2 3 4
    边权为1的询问中有
    询问1,涉及第3条边
    询问1,涉及第4条边
    询问1,涉及第5条边
    询问1,涉及第6条边
    询问2,涉及第3条边
    询问2,涉及第4条边.
    于是先处理询问1,尝试将所要求的边加入,处理完之后,再来处理询问2的。

    #include<bits/stdc++.h>
    #define Rint register int
    using namespace std;
    const int N = 500003;
    int n, m, k, q, mx, fa[N], siz[N];
    bool ans[N];
    struct Edge {
    	int u, v, w;
    	inline bool operator < (const Edge &o) const {return w < o.w;}
    } e[N];
    struct Query {
    	int u, v, w, id;
    	inline bool operator < (const Query &o) const {return id < o.id;}
    };
    vector<Query> vec[N];
    inline int getfa(int x){
    	return x == fa[x] ? x : getfa(fa[x]);
    }
    int stk[N], top;
    inline bool comb(int x, int y)
    {
    	x = getfa(x); y = getfa(y);
    	if(x != y){
    		if(siz[x] > siz[y]) swap(x, y);
    		siz[y] += siz[x]; fa[x] = y; stk[++ top] = x;
    		return true;
    	}
    	return false;
    }
    int main(){
    	scanf("%d%d", &n, &m);
    	for(Rint i = 1;i <= m;i ++) 
    	scanf("%d%d%d", &e[i].u, &e[i].v, &e[i].w), mx = max(mx, e[i].w);
    	scanf("%d", &q);
    	for(Rint i = 1;i <= q;i ++){
    		scanf("%d", &k);
    		while(k --){
    			int now; scanf("%d", &now);
    			vec[e[now].w].push_back((Query){e[now].u, e[now].v, e[now].w, i});
    		}
    	}
    	for(Rint i = 1;i <= mx;i ++) if(!vec[i].empty())
    		sort(vec[i].begin(), vec[i].end());
    	sort(e + 1, e + m + 1);
    	for(Rint i = 1;i <= n;i ++) siz[i] = 1, fa[i] = i;
    	for(Rint i = 1;i <= q;i ++) ans[i] = true;
    	for(Rint i = 1;i <= m;)
    	//按边权,从小到大枚举出某一条边来 
    	{
    		
    		int val = e[i].w; 
    		//取出它的权值 
    		top = 0;
    		for(Rint j = 0;j < vec[val].size();j ++)
    		//枚举边权对应一共有多少个询问,集中处理某一类,再处理另一类询问 
    		{
    			cout<<"j is  "<<j<<endl;
    			if(!ans[vec[val][j].id]) 
    			//这个询问已是无解了 
    			    {
    				cout<<"break"<<endl;
    				continue;
    			   }
    			if(j && vec[val][j].id != vec[val][j - 1].id)
    			//如果处理到另一个询问了的话,则进行撤销操作 
    			{
    				while(top)
    				{
    					siz[fa[stk[top]]] -= siz[stk[top]];
    					fa[stk[top]] = stk[top]; -- top;
    				}
    			}
                cout<<vec[val][j].u<<"   "<<vec[val][j].v<<endl;
    			if(!comb(vec[val][j].u, vec[val][j].v)) 
    			//进行并查集操作,加入边 
    			     ans[vec[val][j].id] = false;
    		}
    		//处理完对于当前边权的询问后,将所有边权为val的边进行合并 
    		//合并出来后,对于某个询问,可能它具体要求的边,并没有用到
    		//但只要这个询问中,规定某个边权需要多少条,现在给它合并上多少条就可以了 
    		while(e[i].w == val)
    		{
    			comb(e[i].u, e[i].v); ++ i;
    		}
    	}
    	for(Rint i = 1;i <= q;i ++) puts(ans[i] ? "YES" : "NO");
    }
    

      

     

      

    https://www.luogu.com.cn/problem/solution/CF891C

  • 相关阅读:
    C++_重载、重写和重定义的区别
    C++静态库与动态库
    C++ 中重载运算符 “<” 及 friend属性
    C++中,关于#include<***.h>和#include"***.h"的区别
    static_cast, dynamic_cast, const_cast讨论
    浅析C++中static关键字
    C语言包含头文件时用引号和尖括号的区别
    vc实现透明位图,透明背景
    VS2008调试技巧——断点失效
    Spring解决循环依赖的理解
  • 原文地址:https://www.cnblogs.com/cutemush/p/16131606.html
Copyright © 2020-2023  润新知