joker(AtCoder - agc044_b)分析

50 阅读2分钟

UPD:25/10/07

vjudge.net/problem/AtC…

是否觉得想不出复杂度ok的方法?那你就是joker了!

 思路分析

每次有一个人离开,这个人的坐标是(x,y),意味着其上下左右可能有人,通过(x,y)离开的怒气值比当前这个人的怒气值小,而新的这个怒气值减小的人,其上下左右的人中,也有可能有人通过这个新的人离开, 会让自身怒气值减小,如此反复,直到没有人的怒气值能够被减小

复杂度分析

每个点最多被更新其初始状态(没有任何人离开)下的怒气值次,比如一个点初始的怒气值是5,每次更新至少-1,那这个点最多减5次变为0,其最多被考虑5次

所以,复杂度上限可以被估计为,初始状态每个人的怒气值的和,经过计算,为n^3数量级

计算方法:初始状态,最外周的人怒气值为0,次外周的人怒气值为1,依次累加

实现

使用occ[x][y](0-没有被占据 1-被占据)表示这个点有没有被占据,使用h[x][y]表示这个点的怒气值,则其周围的点通过这个点离开,其怒气值为occ[x][y]+h[x][y]

    #include<iostream>
    #include<cstdio>
    using namespace std;

    const int maxn=505;
    int h[maxn][maxn],occ[maxn][maxn];
    int n;
    int dx[5]={-1,1,0,0},dy[5]={0,0,-1,1};

    void dfs(int x,int y){
        int z=occ[x][y]+h[x][y];
        for(int i=0;i<4;i++){
            int nx=x+dx[i],ny=y+dy[i];
            if(z<h[nx][ny]){
                h[nx][ny]=z;
                dfs(nx,ny);
            }
        }
    }


    int main()
    {
        cin>>n;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                occ[i][j]=1;
                h[i][j]=min(min(i,n-i+1),min(j,n-j+1))-1;
            }
        }
        long long res=0;
        for(int i=1;i<=n*n;i++){
            int t;cin>>t;
            //获取x,y坐标
            int x=(t+n-1)/n,y=t%n;
            if(y==0) y=n;
            res+=h[x][y];
            occ[x][y]=0;
            dfs(x,y);
        }
        cout<<res<<"\n";
         return 0;
    }

细节(如果没有以下疑惑,可以跳过)

Q1.一个点会不会被反复考虑:从一个点出发,延展出的其他怒气值减小的点,都是经过这个点的,其怒气值只能增加或相等,不会减少,所以一个点不会被反复考虑

Q2.超出边界的点(x=0 y=0 x>n y>n) :其h[x][y]被全局变量初始化为0,因此不可能被考虑