题目:
分析:
前两个点Q=1,写并查集暴力求连通块的个数。3,4个点n=1,考虑把询问离线,用莫队维护。
得分40。。。。
#include<bits/stdc++.h> using namespace std; #define N 4000005 #define nn 2005 #define qq 200005 int a[nn][nn],fa[N],id[nn][nn],tmp[N],Q; int read() { int x=0; int fl=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(fl=='-') fl=-1; ch=getchar(); } while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar(); return x*fl; } int find(int x) { if(x==fa[x]) return x; return fa[x]=find(fa[x]); } void merge(int aa,int b) { int f1=find(aa),f2=find(b); if(f1!=f2) fa[f1]=f2; } void work2() { int x1,x2,y1,y2,cnt=0; x1=read(); y1=read(); x2=read(); y2=read(); for(int i=x1;i<=x2;++i) for(int j=y1;j<=y2;++j) id[i][j]=++cnt; for(int i=1;i<=cnt;++i) fa[i]=i; for(int i=x1;i<=x2;++i) for(int j=y1;j<=y2;++j){ if(a[i][j]==0) continue; if(i-1>=x1 && a[i-1][j]) merge(id[i][j],id[i-1][j]); if(i+1<=x2 && a[i+1][j]) merge(id[i][j],id[i+1][j]); if(j-1>=y1 && a[i][j-1]) merge(id[i][j],id[i][j-1]); if(j+1<=y2 && a[i][j+1]) merge(id[i][j],id[i][j+1]); } int tot=0; for(int i=x1;i<=x2;++i) for(int j=y1;j<=y2;++j) if(a[i][j]) tmp[++tot]=find(id[i][j]); sort(tmp+1,tmp+1+tot); int num=unique(tmp+1,tmp+1+tot)-tmp-1; printf("%d ",num); } struct node{ int l,r,o; } p[qq]; int ans=0,anss[qq]; bool cmp(const node &a,const node &b) { if(a.l==b.l) return a.r<b.r; return a.l<b.l; } void add(int x,int fl) { if(a[1][x]==0) return ; if(a[1][x+fl]==0) ans++; } void del(int x,int fl) { if(a[1][x]==0) return ; if(a[1][x+fl]==0) ans--; } void work1() { int xx; for(int i=1;i<=Q;++i) xx=read(),p[i].l=read(),xx=read(),p[i].r=read(),p[i].o=i; sort(p+1,p+1+Q,cmp); int l=0,r=0; for(int i=1;i<=Q;++i){ while(p[i].l<l) add(--l,1); while(p[i].l>l) del(l++,1); while(p[i].r<r) del(r--,-1); while(p[i].r>r) add(++r,-1); anss[p[i].o]=ans; } for(int i=1;i<=Q;i++) printf("%d ",anss[i]); } char s[nn]; int main() { freopen("duty.in","r",stdin); freopen("duty.out","w",stdout); int n,m; n=read(); m=read(); Q=read(); for(int i=1;i<=n;++i){ scanf("%s",s); for(int j=0;j<=m-1;++j) a[i][j+1]=s[j]-'0'; } if(Q==1) work2(); else work1(); } /* 3 4 1 1101 0110 1101 2 2 3 3 1 6 5 111001 1 3 1 4 1 1 1 5 1 5 1 6 1 2 1 5 1 3 1 6 1 18 3 111001110010101100 1 5 1 17 1 2 1 17 1 1 1 17 */
但是!!!题解说前7个点暴力都可以得70分。。。。(NMQ1e8的复杂度得70分。。。。)
正解:
每个黑色点间最多只有一条边->树。多个连通块就是多棵树,即一个森林。所以说连通块的个数=点数-边数。
快速求点数->维护前缀和。求边数->同样维护前缀和。
如何维护边的前缀和:
在有连边的点之间插一个值,维护那个值的前缀和即可。
注意代码处理细节。
#include<bits/stdc++.h> using namespace std; #define N 4005 int a[N][N],sum[N][N],tot[N][N]; int read() { int x=0; int fl=1; char ch=getchar(); while(ch>'9'||ch<'0') { if(fl=='-') fl=-1; ch=getchar(); } while(ch<='9'&&ch>='0') x=x*10+ch-'0',ch=getchar(); return x*fl; } char s[N]; int main() { freopen("duty.in","r",stdin); freopen("duty.out","w",stdout); int n,m,Q; n=read(); m=read(); Q=read(); for(int i=1;i<=2*n;i+=2){//奇数行奇数列的是原数组 scanf("%s",s); for(int j=1;j<=2*m;j+=2) a[i][j]=s[j/2]-'0'; } for(int i=1;i<=2*n;i++){ if(i&1){ for(int j=2;j<=2*m;j+=2) if(a[i][j-1] && a[i][j+1]) a[i][j]=6;//标记为连边 } else{ for(int j=1;j<=2*m;j+=2) if(a[i-1][j] && a[i+1][j]) a[i][j]=6; } } for(int i=1;i<=2*n;i++)//处理前缀和 for(int j=1;j<=2*m;j++) sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(a[i][j]==1); for(int i=1;i<=2*n;i++) for(int j=1;j<=2*m;j++) tot[i][j]=tot[i-1][j]+tot[i][j-1]-tot[i-1][j-1]+(a[i][j]==6); while(Q--){ int x1,x2,y1,y2; x1=read(); y1=read(); x2=read(); y2=read(); x1=x1*2-1,x2=x2*2-1,y1=y1*2-1,y2=y2*2-1; int ans1=sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1]; int ans2=tot[x2][y2]-tot[x2][y1-1]-tot[x1-1][y2]+tot[x1-1][y1-1]; printf("%d ",ans1-ans2);//ans=点数-边数 } } /* 3 4 1 1101 0110 1101 //样例输出来的样子 1 6 1 0 0 0 1 0 0 0 6 0 0 0 0 0 0 0 1 6 1 0 0 0 0 0 6 0 0 0 0 0 1 6 1 0 0 0 1 0 0 0 0 0 0 0 0 0 */