Nowcoder9981D.点一成零(并查集+线段树)
链接:https://ac.nowcoder.com/acm/contest/9981/D
来源:牛客网
牛牛拿到了一个n*n的方阵,每个格子上面有一个数字:0或1
行和列的编号都是从0到n-1
现在牛牛每次操作可以点击一个写着1的格子,将这个格子所在的1连通块全部变成0。
牛牛想知道,自己有多少种不同的方案,可以把全部格子的1都变成0?
所谓连通块,是指方阵中的两个正方形共用一条边,即(x,y)和以下4个坐标的数是连通的:(x-1,y)、(x+1,y)、(x,y-1)、(x,y+1)
这个问题对于牛牛来说可能太简单了。于是他将这个问题变得更加复杂:
他会选择一个格子,将这个格子上的数字修改成1(如果本来就是1,那么不进行任何改变),再去考虑“点一成零”的方案数。
牛牛想知道,每次“将某个格子修改成1”之后,“把全部格子的1都变成0”的方案数量。
ps:请注意,每次“将某个格子修改成1”之后,状态会保留到接下来的询问。具体请参考样例描述。
由于方案数可能过大,请对109+710^{9}+7109+7取模
//每次加边 //查询周围四个格子是不是属于一个联通快 //如果是,就合并,同时更新当前联通快数量 //如果不是,就不变 #include<bits/stdc++.h> using namespace std; const int maxn=550; const int mod=1e9+7; //方案是!x,连通快数量 typedef long long ll; int a[maxn][maxn]; int X[4]={1,0,-1,0}; int Y[4]={0,1,0,-1}; ll f[maxn*maxn];//表示阶乘 string s[maxn]; int father[maxn*maxn]; int num[maxn*maxn];//每个父亲联通快1的数量 int n; struct node { int l,r; ll sum; }segTree[maxn*maxn*4]; void build (int i,int l,int r) { segTree[i].l=l; segTree[i].r=r; if (l==r) { segTree[i].sum=num[l]; return; } int mid=(l+r)>>1; build(i<<1,l,mid); build(i<<1|1,mid+1,r); segTree[i].sum=(segTree[i<<1].sum==0?1:segTree[i<<1].sum)*(segTree[i<<1|1].sum==0?1:segTree[i<<1|1].sum)%mod; segTree[i].sum%=mod; } void up (int i,int p,int v) { if (segTree[i].l==p&&segTree[i].r==p) { segTree[i].sum=v; return; } int mid=(segTree[i].l+segTree[i].r)>>1; if (p<=mid) up(i<<1,p,v); if (p>mid) up(i<<1|1,p,v); segTree[i].sum=(segTree[i<<1].sum==0?1:segTree[i<<1].sum)*(segTree[i<<1|1].sum==0?1:segTree[i<<1|1].sum)%mod; segTree[i].sum%=mod; } void add (int i,int p,int v) { if (segTree[i].l==p&&segTree[i].r==p) { segTree[i].sum+=v; return; } int mid=(segTree[i].l+segTree[i].r)>>1; if (p<=mid) add(i<<1,p,v); if (p>mid) add(i<<1|1,p,v); segTree[i].sum=(segTree[i<<1].sum==0?1:segTree[i<<1].sum)*(segTree[i<<1|1].sum==0?1:segTree[i<<1|1].sum)%mod; segTree[i].sum%=mod; } ll query (int i,int l,int r) { if (segTree[i].l>=l&&segTree[i].r<=r) { return segTree[i].sum; } int mid=(segTree[i].l+segTree[i].r)>>1; ll ans=1; if (l<=mid) ans*=max(1ll,query(i<<1,l,r)); if (r>mid) ans*=max(1ll,query(i<<1|1,l,r)); return ans; } int findfather (int x) { int a=x; while (x!=father[x]) x=father[x]; while (a!=father[a]) { int z=a; a=father[a]; father[z]=x; } return x; } void Union (int x,int y) { x=findfather(x); y=findfather(y); if (x==y) return; father[x]=y; num[y]+=num[x]; num[x]=0; up(1,x,num[x]); up(1,y,num[y]); } int judge (int x,int y) { if (x<1||x>n||y<1||y>n) return 0; return 1; } int isRoot[maxn*maxn]; int main () { scanf("%d",&n); f[0]=1; for (int i=1;i<=n*n;i++) father[i]=i; for (int i=1;i<=n*n;i++) f[i]=(f[i-1]*i)%mod; for (int i=0;i<n;i++) cin>>s[i]; for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) a[i][j]=s[i-1][j-1]-'0',num[(i-1)*n+j]=a[i][j]; build(1,1,n*n); for (int i=1;i<=n;i++) { for (int j=1;j<=n;j++) { for (int k=0;k<4;k++) { int x=i+X[k]; int y=j+Y[k]; if (!judge(x,y)) continue; if (a[i][j]&&a[x][y]) { Union((i-1)*n+j,(x-1)*n+y); } } } } for (int i=1;i<=n;i++) for (int j=1;j<=n;j++) if (a[i][j]==1) isRoot[findfather((i-1)*n+j)]++; int ans=0; for (int i=1;i<=n*n;i++) if (isRoot[i]) ans++; //printf("%d\n",ans); int q; scanf("%d",&q); //方案 //动态维护当前连通块的数量 //同时用线段树统计所有1的连通块乘积 //在线段树里更新父亲的值即可 while (q--) { int x,y; scanf("%d%d",&x,&y); x++; y++; if (a[x][y]==1) { //printf("%lld\n",ans,f[ans]); ll tt=1; tt=segTree[1].sum*f[ans]%mod; tt%=mod; printf("%lld\n",tt); continue; } set<int> st; for (int k=0;k<4;k++) { int xx=x+X[k]; int yy=y+Y[k]; if (!judge(xx,yy)) continue; if (a[xx][yy]==0) continue; st.insert(findfather((xx-1)*n+yy)); } ans-=(st.size()-1); a[x][y]=1; num[(x-1)*n+y]++; for (int k=0;k<4;k++) { int xx=x+X[k]; int yy=y+Y[k]; if (!judge(xx,yy)) continue; if (a[xx][yy]==0) continue; Union((x-1)*n+y,(xx-1)*n+yy); } // for (int i=1;i<=n;i++) { // for (int j=1;j<=n;j++) { // printf("%d ",a[i][j]); // } // printf("\n"); // } ll tt=1; tt=segTree[1].sum*f[ans]%mod; tt%=mod; printf("%lld\n",tt); //printf("%d %lld\n",ans,f[ans]); } }