zoukankan      html  css  js  c++  java
  • 牛客寒假算法训练营1-D

    并查集、求逆元、排列组合

    题目传送门

    解题思路

    首先要求出没有修改时的方案数量,再考虑修改一个格子之后相对于之前的变化

    1 求没有修改时方案数量

    首先使用并查集操作求出连通块的个数每个连通块的大小

    假设有(cnt)个连通块,然后每个连通块的大小是(size_i , iin [1, cnt])

    那么根据题意,利用排列组合的简单知识可以知道,总的方案数为:(ans = cnt!*prod_{i=1}^{cnt}size_i)

    2 修改了一个格子相对于之前的变化

    将一个各自的数字变成1有两种情况

    1. 这个格子原本就是1,那么直接输出上次的ans即可
    2. 这个格子原本是0

    对于第二种情况,若(a[x][y] == '0') , 我们可以这样理解:

    step1:

    首先我们把它变成('1'),把他当成一个独立的连通块,则此时(cnt = cnt + 1; ans = ans*cnt)

    step2:

    然后看坐标((x,y))的上下左右四个方向是不是('1'),如果是的话,就将这个((x,y))所在的连通块和该方向的点所在的联通块合并。

    例如: (a[x-1][y]=='1'), 那么 假设 (a[x][y])所在连通块的根是(f_1),大小是(size_1)(a[x-1][y])所在连通块的根是(f_2),大小是(size_2),这两个连通块可以合并,合并之后方案数为:(ans = (ans/cnt/size_1/size_2)*(size_1 + size_2))


    知识点:逆元

    有两种求逆元的方法:扩展欧里几德算法求逆元、费马小定理求逆元

    费马小定理求逆元德方法比较简单,但是适用范围比较小

    p是质数,且a、p互质,则: ((a/b)\%p = (a*a^{p-2})\%p),其中(a^{p-2})使用快速幂算法求


    ac代码

    #include<bits/stdc++.h>
    typedef long long ll;
    using namespace std;
    const int MaxN = 510;
    const int mod = 1e9+7;
    int n, k, x, y;
    char a[MaxN][MaxN];
    int fa[MaxN*MaxN]; // 
    int size[MaxN*MaxN];
    int cnt = 0; // 连通块的个数 
    ll ans = 1;
    int dx[] = {0, 0, -1, 1};
    int dy[] = {-1, 1, 0, 0};
    // 并查集的并、查操作
    int find(int x)
    {
    	return fa[x] == x ? x : fa[x] = find(fa[x]);
     } 
    void unionn(int x, int y)
    {
    	int fax = find(x);
    	int fay = find(y);
    	if(fax !=  fay){
    		size[fax] += size[fay];
    		fa[fay] = fax;
    	}
    }
    ll ksm(int n , int k)
    {
    	ll ans = 1;
    	ll tot = n;
    	while(k){
    		if(k&1) ans = ans*tot%mod;
    		tot = tot*tot%mod;
    		k >>= 1;
    	}
    	return ans%mod;
    }
    // 求逆元
    inline ll inv(int x)
    {
    	return ksm(x, mod-2);
    }
    int main()
    {
    	scanf("%d", &n);
    	for(int i=1; i<=n; ++i){
    		scanf(" %s", a[i]+1);  // 左上角的坐标从(1,1)开始计算
    	}
    
    //	printf("*********
    ");
    //	for(int i=1; i<=n; ++i){
    //		for(int j=1; j<=n; ++j){
    //			printf("%c", a[i][j]);
    //		}
    //		printf("
    ");
    //	}
    //	printf("**********
    ");
    	// 初始化 
    	for(int i=0; i<=(n+1)*(n+1); ++i){ // 一定注意这里是(N+1)*(n+1), 也可以直接在上面n++
    		fa[i] = i;
    		size[i] = 1;
    	}
    	// 合并连通块 
    	for(int i=1; i<=n; ++i){
    		for(int j=1; j<=n; ++j){
    			if(a[i][j]=='1'){  // 看上下左右有无‘1’ 有的话合并 
    				if(a[i-1][j]=='1') unionn(i*n+j, (i-1)*n+j);
    				if(a[i+1][j]=='1') unionn(i*n+j, (i+1)*n+j);
    				if(a[i][j-1]=='1') unionn(i*n+j, i*n+j-1);
    				if(a[i][j+1]=='1') unionn(i*n+j, i*n+j+1);
    			}
    		}
    	}
    	// 求连通块的个数 // 计算初始解
    	for(int i=1; i<=n; ++i){
    		for(int j=1; j<=n; ++j){
    			if(a[i][j]=='1' && fa[i*n+j] == i*n+j) {
    				cnt++;
    				ans = (ans * size[fa[i*n+j]])%mod;	
    			}
    		}
    	} 
    	for(int i=1; i<=cnt; ++i){
    		ans = (ans*i)%mod;
    	}
    	
    	scanf("%d", &k);
    	while(k--){
    		scanf("%d %d",&x, &y);
    		x++, y++;
    		if(a[x][y]=='1'){
    			printf("%lld
    ",ans);
    			continue;	
    		}
    		a[x][y] = '1';
    //		fa[x*n+y] = fa[x*n+y]; // 这个多余,可以去掉 
    		// 先将这个'1'当成一个单独的连通块,计算ans 
    		cnt++;
    		ans = (ans*cnt)%mod;
    		for(int i=0; i<4; ++i){
    			int xx = x+dx[i];
    			int yy = y+dy[i];
    			if(a[xx][yy]=='1'){
    				int f1 = find(x*n+y);
    				int f2 = find(xx*n+yy);
    				if(f1!=f2){ // 两个连通块合并 
    					// ans = ans/cnt/size[f1]/size[f2]*(size[f1]+size[f2])%mod
    					ans = (ans*inv(cnt))%mod;
    					ans = (ans*inv(size[f1]))%mod;
    					ans = (ans*inv(size[f2]))%mod;
    					ans = (ans*(size[f1]+size[f2]))%mod;
    					unionn(f1, f2); // 合并连通块 
    					cnt--; // 连通块数量-1 
    					
    				}
    				 
    			}
    		}
    		printf("%lld
    ",ans);
    	}
    	return 0;
    }
     
    

    参考链接

    https://ac.nowcoder.com/acm/contest/view-submission?submissionId=46654023

    https://www.cnblogs.com/kongbursi-2292702937/p/10582258.html

    https://www.cnblogs.com/thornblog/p/11889737.html

  • 相关阅读:
    Drop goldengate用户时报ORA-00604 ORA-20782 ORA-06512问题解决
    如何查看机器品牌型号
    ORA-28040:没有匹配的验证协议
    oracle 12C CDB下开启wallet
    mysql备份和恢复
    手工模拟vip切换
    半同步复制的安装部署
    linux下安装mysql5.7.11(二进制方式)
    postgrep创建存储过程例子
    Nginx 是前端工程师的好帮手
  • 原文地址:https://www.cnblogs.com/VanHa0101/p/14459783.html
Copyright © 2011-2022 走看看