• 暑假考试题9:duty 任(前缀和)


    题目:

     

     

     

     分析:

    前两个点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
    */
    正解
  • 相关阅读:
    jQuery学习总结之基础知识持续更新中
    经典人生感悟 看看你少了那一条
    [SQL]清空数据方法大比拼
    2010年的最后一天
    javascript学习之闭包
    实现checkbox的全选/全不选/点选/行内点选(原生JS版和jQ版)
    挖掘经典:几乎被人遗忘的HTML七种用法 (转)
    下拉及多及弹出式菜单
    win7下注册一个com失败,权限不够
    Visual Studio 2010 自述文件
  • 原文地址:https://www.cnblogs.com/mowanying/p/11439780.html
Copyright © 2020-2023  润新知