zoukankan      html  css  js  c++  java
  • HDU 5925 Coconuts

    2016 CCPC 东北四省赛 D.
    一道好题.
    现场写崩了.
    赛后LSh跟我讲了一种离散化的做法, 没听懂.

    题意

    一个$R cdot C (R, Cle 10^9)$ 的矩形点阵上有 $n (n le 200) $ 个坏点, 其余都是好点.
    好点的4-连通块 (以下简称cell) 的个数和每块的大小.

    做法

    我的想法是:
    找出一个坏点的8-连通块, 在框住这个块的(最小)矩形区域内, 搜索当前8-连通块以及整个矩形点阵的边界 (以下简称"fence") 包围的cell.

    这个想法大体上没什么漏洞.
    要注意的问题有

    1. fence 套 fence 的情况
    2. dfs的各种边界情况的顺序 (我是用dfs搜索好点的4-连通块的)

    对于fence 套 fence 的情况, 如果对两个fence不加区分 就会导致重复统计.
    Solution: 对每个 (可能的) fence 上的坏点编号.

    实现上的坑

    这题的坑真多 (至少对蒟蒻我而言如此)
    一开始核心代码是这样写的:

    int dx[]{-1, 1, 0, 0}, dy[]{0, 0, 1, -1};
    
    
    bool used[205][205];
    
    int dfs(int _x, int _y){
        if(_x==0 || _x==r+1 || _y==0 || _y==c+1){
            return -2;
        }
    
        for(int i=0; i<n; i++){
            if(_x==x[i] && _y == y[i]){
                return vis[i]==id?0:-3;
            }
        }
        // good nut
        if(_x<x1 || _x > x2 || _y < Y1 || _y > y2){
            return -1;
        }
    
        if(used[_x-x1][_y-Y1]){
            return 0;
        }
    
        used[_x-x1][_y-Y1]=true;
        int res=0;
    
        bool flag=false;
    
        for(int i=0; i<4; i++){
            int nx=_x+dx[i], ny=_y+dy[i];
            int tmp = dfs(nx, ny);
    
            if(tmp == -1){
                return -1;
            }
    
            else if(tmp>=0) flag=true;
    
            else if(tmp>0) res+=tmp;
        }
    
        return flag?res+1:0;
    }
    

    这个dfs的目的是搜索好点的4-连通块, 同时判断该连通块是否合法.

    然而我这种用返回值判断合法性的dfs写法却隐藏着一个bug:

    注意dfs中的这样一段代码:

    for(int i=0; i<4; i++){
        int nx=_x+dx[i], ny=_y+dy[i];
        int tmp = dfs(nx, ny);
    
        if(tmp == -1){
            return -1;
        }
    
        else if(tmp>=0) flag=true;
    
        else if(tmp>0) res+=tmp;
    }
    

    其中的

    if(tmp == -1){
        return -1;
    }
    

    当dfs到一个超出当前矩形框的点时, 就会返回-1.
    这样的写法在某些情况下会让好点也成为fence的一部分, 从而无法搜出一个好点的4-连通块. (这一点还会详谈)

    比较好的写法:
    vis数组+全局变量

    一个好点的连通块合法的条件:

    1. 不超出矩形框
    2. 至少能遇到一个当前fence上的坏点

    我们用两个全局的bool值 (flag) 来表示这两个条件是否成立, 再用一个全局变量记录当前连通块的大小.

    Implementation

    #include <bits/stdc++.h>
    using namespace std;
    
    const int N=205;
    
    
    int x[N], y[N];
    int vis[N];
    
    // vector<int> st;
    
    int r, c, n;
    
    int x1, x2, Y1, y2;
    int id;
    
    void dfs(int i){
        // st.push_back(i);
        vis[i]=id;
        x1=min(x1, x[i]);
        x2=max(x2, x[i]);
        Y1=min(Y1, y[i]);
        y2=max(y2, y[i]);
    
        for(int dx=-1; dx<=1; dx++)
            for(int dy=-1; dy<=1; dy++){
                int nx=x[i]+dx, ny=y[i]+dy;
                for(int j=0; j<n; j++){
                    if(!vis[j] && x[j]==nx && y[j]==ny){
                        dfs(j);
                        break;  // ?
                    }
                }
            }
    }
    
    int dx[]{-1, 1, 0, 0}, dy[]{0, 0, 1, -1};
    
    
    bool used[205][205];
    bool f1, f2;
    int cnt;
    
    void dfs(int _x, int _y){
    	// on border
    	if(_x==0 || _x==r+1 || _y==0 || _y==c+1){
    	    return;
    	}
    	// out of range
    	if(_x<x1 || _x > x2 || _y < Y1 || _y > y2){
    	    f1=false;
    	    return;
    	}
    	// bad nut
        for(int i=0; i<n; i++){
            if(_x==x[i] && _y == y[i]){
                if(vis[i]==id)
                	f2=true;
                return;
            }
        }
    
        // good nut
        if(used[_x-x1][_y-Y1]){
            return;
        }
        // cout<<_x<<' '<<_y<<endl;
        used[_x-x1][_y-Y1]=true;
        ++cnt;
    
        for(int i=0; i<4; i++){
            int nx=_x+dx[i], ny=_y+dy[i];
            dfs(nx, ny);
        }
    }
    
    vector<long long> res;
    long long tot;
    
    void clac(){
        for(int i=x1; i<=x2; i++)
            for(int j=Y1; j<=y2; j++){
                used[i-x1][j-Y1]=false;
            }
    
        for(int i=x1; i<=x2; i++)
            for(int j=Y1; j<=y2; j++){
            	f1=true, f2=false;
            	cnt=0;
                dfs(i, j);
                // cout<<cnt<<endl;
                // if(f1) puts("f1");
                // if(f2) puts("f2");
                if(f1 && f2 && cnt>0){
                	tot-=cnt;
                	res.push_back(cnt);
                }
            }
    }
    
    
    
    void solve(int n){
        res.clear();
        for(int i=0; i<n; i++){
            vis[i]=0;
        }
    
        tot=(long long)r*c-n;
        id=0;
    
        for(int i=0; i<n; i++){
            if(!vis[i]){
                x1=Y1=INT_MAX;
                x2=y2=INT_MIN;
                ++id;
                dfs(i);
                clac();
            }
        }
    
        if(tot>0) res.push_back(tot);
    
        sort(res.begin(), res.end());
        printf("%lu
    ", res.size());
    
        for(size_t i=0; i<res.size(); i++){
            if(i) putchar(' ');
            printf("%lld", res[i]);
        }
    
        puts("");
    }
    
    int main(){
        int T, cas=0;
        for(cin>>T; T--; ){
            printf("Case #%d:
    ", ++cas);
    
            cin>>r>>c>>n;
    
            for(int i=0; i<n; i++){
                scanf("%d%d", x+i, y+i);
            }
            solve(n);
        }
        return 0;
    }
    

    另外一个更隐蔽的坑:
    DFS 的三个边界情况 (点阵边界, 超出矩形框, 坏点) 的顺序.

  • 相关阅读:
    Redis 外网无法访问
    设计模式单例模式
    网易笔试问答(20210328)新生代转移到老年代的情况
    网易笔试(20210327)最长摇摆子数组长度
    网易笔试(20210327)吉利数(子数组中数组和为6的倍数的最大子数组和)
    360笔试(20210328)射气球
    解决TFS 错误:[TF15013 请求的 Team Foundation Server 未向代理服务器注册]
    TFS 常用命令
    生成安全的用于加密的随机数
    一种新的验证码(改进版)
  • 原文地址:https://www.cnblogs.com/Patt/p/5985906.html
Copyright © 2011-2022 走看看