并查集专题(带权,启发式合并)

算是一个模板题吧

给出[l,r]的区间和,相当于s[r]-s[l]

一旦已经知道了 s[a]-s[b],s[b]-s[c],显然再给出一条[a,c]就可以判断"账本的真假"了

将每条这样的信息(l,r,w),l,r放入一个集合中,

用并查集来维护,并维护cha[l]=s[root]-s[l],cha[r]=s[root]-s[r]

若 l,r已经在同一个集合中,就直接查询cha[l]-cha[r],判读与w是否相等

合并操作是灵魂

实话说如果没有对并查集合并操作和路径压缩理解够深刻 是不能写出来的

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=505;
int T;
int fa[maxn],val[maxn];
int find(int x){
	if(fa[x]==x)return x;
	else {
		int oldfa=fa[x];
		fa[x]=find(fa[x]);
		val[x]+=val[oldfa];
		return fa[x];
	}
}
void solve();
int main(){
	cin>>T;
	while(T--)solve();
     return 0;
}
void solve(){
	int n,m;
	cin>>n>>m;
	for(int i=0;i<=n;i++)
	fa[i]=i,val[i]=0;
    bool cmp=1;
	while(m--){
		int x,y,z;
		cin>>x>>y>>z;
		x--;
		int fx=find(x),fy=find(y);
		if(fx!=fy){
			fa[fy]=fx;
			val[fy]=val[x]-val[y]-z; 
		}
		else if(val[x]-val[y]!=z)cmp=0;
	}
	if(cmp)cout<<"true"<<endl;
	else cout<<"false"<<endl;
}

每次给出两个昆虫的关系(异性关系),然后发现这些条件中是否有悖论

和上一个题目差不多 维护x->rootx 的距离就好

#include <cstdio>

const int maxn = 2000 + 10;

int pre[maxn];
int r[maxn];  // 与根节点的关系,如果值为1则为异性如果为0则为同性

int find(int x) {
    int t = pre[x];
    if (pre[x] != x) {
        pre[x] = find(pre[x]);
        r[x] = (r[x] + r[t]) % 2;  // 更新关系
    }
    return pre[x];
}

int main() {
   // freopen("input.txt", "r", stdin);
    int flag;
    int kase = 0;
    int T;
    int x, y;
    int n, m;
    scanf("%d", &T);
    while(T--) {
         flag = 1;
         scanf("%d%d", &n, &m);
         for(int i = 1; i <= n; i++) {
             pre[i] = i;
             r[i] = 0;
         }
         while(m--) {
             scanf("%d%d", &x, &y);
             if (!flag) continue;
             int rx = find(x);
             int ry = find(y);
             if (rx == ry) {
                if ((r[x] - r[y]+2) % 2 == 0) {
                    flag = 0;
                }
             }
             else {
                 pre[ry] = rx;
                 r[ry] = (r[x] - r[y] + 1) % 2;
             }
         }
         printf("Scenario #%d:\n",++kase);
         if(flag)
                printf("No suspicious bugs found!\n\n");
         else
                printf("Suspicious bugs found!\n\n");
    }

    return 0;
}

这个题是上面的题型的升级版 首先找到规律

0吃1
1吃2
2吃0(2吃3)
发现后面减去前面始终为1 所以就是 被吃 减 吃==1

剩下的就差不太多了

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=5e4+5;
int n,m,ans; 
int fa[maxn],val[maxn];
int find(int x){
	if(x==fa[x])return x;
	int oldfa=fa[x];
	fa[x]=find(fa[x]);
	val[x]=(val[x]+val[oldfa])%3;
	return fa[x];
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)fa[i]=i,val[i]=0;
	for(int i=1;i<=m;i++){
		int c,x,y;
		cin>>c>>x>>y;
		if(x>n||y>n||(c==2&&x==y)){
			ans++;continue;
		}
		int fx=find(x),fy=find(y);
		if(c==1){
			if(fx!=fy){
			 fa[fy]=fx;
			 val[fy]=(val[x]-val[y]+3)%3; 
			}else if((val[y]-val[x]+3)%3)
					ans++;
		}else{
			if(fx==fy){
				if(((val[y]-val[x]+3)%3)!=1)
				ans++;
			}
			else{
				fa[fy]=fx;
				val[fy]=(val[x]-val[y]+1+3)%3;
			}
		}
	}
	cout<<ans<<endl;
     return 0;
}

因为朋友之间是双向边 只要在合并的时候维护一下集合的最小值就好 最后每个集合只要保证有一个最小的人去贿赂就好

思考:如果是单向边的话 那就要tarjan缩点 然后对每个缩完点入度为0的点中找出最小的一个 累加

启发式合并
看似暴力实则很快的算法

启发式算法是基于人类的经验和直观感觉,对一些算法的优化。

而启发式合并就是一个比较经典的算法,并且复杂度式有保证的。

启发式合并就是在合并的时候将size小的那个集合合并到size大的那个集合里面。

比如[1,2,3] 和 [3,5,6,7] 合并,我们选择遍历前者来把元素放入后者。

初看上可能感觉这就是个暴力。但是我们分析一下每个元素被push_back()了多少次。

一个集合中的元素被放入另一个集合中会被push_back()一次。

但是这个元素所在的集合的大小至少扩大了一倍。所以一个元素最多被push_back()了 log(n)次。

例题:
https://www.luogu.com.cn/problem/P3201

很明显的一个启发式合并 注意题目中强调了是 x 变为 y 所以必须要开一个映射数组才能满足启发式合并

映射的作用就是 x 变为 y 如果sz[x]>sz[y] 此时就应该是y合并到x上 只要最后两者映射交换一下就好

这个题还有一点就是 操作只会让ans变小 最优的操作是ans不变

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=1e6+5;
int m,n,ans,x,y,op;
vector<int>Q[maxn]; 
int now[maxn],col[maxn];
void merge(int,int);
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		scanf("%d",&col[i]);
		if(col[i]!=col[i-1])ans++;
		now[col[i]]=col[i];
		Q[col[i]].push_back(i);
	}
	while(m--){
		scanf("%d",&op);
		if(op==2)printf("%d\n",ans);
		else{
			scanf("%d%d",&x,&y);
			if(x==y)continue;
			if(Q[now[x]].size()>Q[now[y]].size())swap(now[x],now[y]);
			merge(now[x],now[y]);
		}
	}
     return 0;
}
void merge(int xx,int yy){
	for(int i=0;i<Q[xx].size();i++){
		if(col[Q[xx][i]-1]==yy)ans--;
		if(col[Q[xx][i]+1]==yy)ans--;
	}
	for(int i=0;i<Q[xx].size();i++)
	col[Q[xx][i]]=yy,Q[yy].push_back(Q[xx][i]);
	Q[xx].clear();
}

https://leetcode.cn/problems/detect-cycles-in-2d-grid/

对于此类二维空间的并查集 可以将二维的点对转化成一维来计算

class Solution {
    boolean[][] visit; 
    UnionFind uf;
    public boolean containsCycle(char[][] grid) {
         int m = grid.length;
         int n = grid[0].length;
         visit = new boolean[m][n];
         uf = new UnionFind(m*n);
         for(int i=0;i<m;i++){
             for(int j =0;j<n;j++){
                 if(visit[i][j] == false){
                   boolean flag = dfs(grid,i,j);
                   if(flag == true)
                      return true;
               }
             }
         }
         return false;
    }
    public boolean dfs(char[][] grid,int i,int j){
         visit[i][j] = true;
         int m = grid.length;
         int n = grid[0].length;
         boolean union1 = false;
         boolean union2 = false;
         if(i+1 < m && grid[i][j] == grid[i+1][j])
         {
             boolean union = uf.Union(i*n+j,(i+1)*n+j);
             if(union == false)
                 return true;
             if(visit[i+1][j] == false)
                union1 = dfs(grid,i+1,j);
         }
         if(j+1 < n && grid[i][j] == grid[i][j+1])
         {
             boolean union = uf.Union(i*n+j,i*n+j+1);
             if(union == false)
                 return true;
             if(visit[i][j+1] == false)
               union2 = dfs(grid,i,j+1);   
         }
         if(union1==true||union2==true)
            return true;
         else
            return false;
    }
    public class UnionFind{
        int[] parent;
        int count;
        public int getCount(){
            return count;
        }
        public UnionFind(int n){
            parent = new int[n];
            count = n;
            for(int i =0;i<n;i++){
                parent[i] = i;
            }
        }
        public int find(int x){
            if(parent[x] != x)
               parent[x] = find(parent[x]);
            return parent[x];
        }
        public boolean Union(int x,int y){
            int rootX = find(x);
            int rootY = find(y);
            if(rootX == rootY)
                return false ;
            parent[rootX] = rootY;
            count -= 1;
            return true; 
        }
    }
}

XMU 1614 刘备闯三国之三顾茅庐(二)

题目大意:

  一个N*M的矩形网格,Q个操作,每次操作会输入x1 y1 x2 y2,使(x1,y1)到(x2,y2)的成为障碍(保证x1=x2 或y1=y2,为一行或一列的连续格子)

  问每次操作后网格内的联通块数量(不含障碍)。

分析:

同样是二维并查集 对于每次加一道障碍不是很好算贡献

考虑逆向思维 先把边给建立好 再倒序依次删边即可

代码写的并查集是开了两个数组 其实完全没有必要 直接将二维坐标变为一维坐标即可

#include<bits/stdc++.h>
#pragma comment(linker,"/STACK:1024000000,1024000000")
#define abs(a) ((a)>0?(a):(-(a)))
#define lowbit(a) (a&(-a))
#define sqr(a) ((a)*(a))
#define mem(a,b) memset(a,b,sizeof(a))
const double EPS=1e-8;
const int J=10;
const int MOD=100000007;
const int MAX=0x7f7f7f7f;
const double PI=3.14159265358979323;
const int N=1004;
const int M=10004;
using namespace std;
typedef long long LL;
double anss;
LL aans;
int cas,cass;
int n,m,lll,ans;
int num[N][N],sz[N][N];
int fa[N][N][2];
int f[M];
int dx[]={-1,0,1,0};
int dy[]={0,1,0,-1};
struct xxx
{
    int x1,y1,x2,y2;
}q[M];
void zhao(int x,int y,int &fx,int &fy)
{
    if((fa[x][y][0]==x && fa[x][y][1]==y) || (!fa[x][y][0] && !fa[x][y][1]))
    {
        fx=x,fy=y;
        return;
    }
    zhao(fa[x][y][0],fa[x][y][1],fx,fy);
    fa[x][y][0]=fx;
    fa[x][y][1]=fy;
}
void print()
{
    int i,j;
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=m;j++)
            printf("%d ",num[i][j]);
        puts("");
    }
    puts("");
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=m;j++)
            printf("%d,%d ",fa[i][j][0],fa[i][j][1]);
        puts("");
    }
    puts("");
    for(i=1;i<=n;i++)
    {
        for(j=1;j<=m;j++)
            printf("%d ",sz[i][j]);
        puts("");
    }
    puts("");
}
int main()
{
    int i,j,k;
    int x,y,z;
    while(~scanf("%d",&n))
    {
        mem(num,0);mem(sz,0);mem(fa,0);mem(f,0);
        scanf("%d%d",&m,&cass);
        for(i=1;i<=cass;i++)
        {
            scanf("%d%d%d%d",&q[i].x1,&q[i].y1,&q[i].x2,&q[i].y2);
            if(q[i].x1==q[i].x2)
            {
                if(q[i].y1>q[i].y2)swap(q[i].y1,q[i].y2);
                for(j=q[i].y1;j<=q[i].y2;j++)
                    num[q[i].x1][j]++;
            }
            else
            {
                if(q[i].x1>q[i].x2)swap(q[i].x1,q[i].x2);
                for(j=q[i].x1;j<=q[i].x2;j++)
                    num[j][q[i].y1]++;
            }
        }
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                for(k=0;k<4;k++)
                {
                    x=i+dx[k];
                    y=j+dy[k];
                    if(x<1 || x>n || y<1 || y>m || num[i][j] || num[x][y])continue;
                    int fx1,fy1,fx2,fy2;
                    zhao(i,j,fx1,fy1);
                    zhao(x,y,fx2,fy2);
                    if(fx1==fx2 && fy1==fy2)continue;
                    fa[fx2][fy2][0]=fx1;
                    fa[fx2][fy2][1]=fy1;
                }
            }
        }
        for(i=1;i<=n;i++)
        {
            for(j=1;j<=m;j++)
            {
                zhao(i,j,fa[i][j][0],fa[i][j][1]);
                if(!sz[fa[i][j][0]][fa[i][j][1]] && !num[i][j])f[cass+1]++;
                sz[fa[i][j][0]][fa[i][j][1]]++;
            }
        }
        for(i=cass;i>1;i--)
        {
            f[i]=f[i+1];
            if(q[i].x1==q[i].x2)
            {
                for(j=q[i].y1;j<=q[i].y2;j++)
                {
                    num[q[i].x1][j]--;
                    if(num[q[i].x1][j]==0)
                    {
                        f[i]++;
                        for(k=0;k<4;k++)
                        {
                            x=q[i].x1+dx[k];
                            y=j+dy[k];
                            if(x<1 || x>n || y<1 || y>m || num[x][y])continue;
                            int fx1,fy1,fx2,fy2;
                            zhao(q[i].x1,j,fx1,fy1);
                            zhao(x,y,fx2,fy2);
                            if(fx1==fx2 && fy1==fy2)continue;
                            fa[fx2][fy2][0]=fx1;
                            fa[fx2][fy2][1]=fy1;
                            f[i]--;
                        }
                    }
                }
            }
            else
            {
                for(j=q[i].x1;j<=q[i].x2;j++)
                {
                    num[j][q[i].y1]--;
                    if(num[j][q[i].y1]==0)
                    {
                        f[i]++;
                        for(k=0;k<4;k++)
                        {
                            x=j+dx[k];
                            y=q[i].y1+dy[k];
                            if(x<1 || x>n || y<1 || y>m || num[x][y])continue;
                            int fx1,fy1,fx2,fy2;
                            zhao(j,q[i].y1,fx1,fy1);
                            zhao(x,y,fx2,fy2);
                            if(fx1==fx2 && fy1==fy2)continue;
                            fa[fx2][fy2][0]=fx1;
                            fa[fx2][fy2][1]=fy1;
                            f[i]--;
                        }
                    }
                }
            }
        }
        for(i=2;i<=cass+1;i++)
            printf("%d\n",f[i]);
        //print();
    }
    return 0;
}
/*
//

//
*/
posted @ 2022-04-23 22:13  wzx_believer  阅读(166)  评论(0)    收藏  举报