题意

给你一个图片说明 的矩形范围,你可以用一个宽度最多为图片说明 ,高度为图片说明 的十字架覆盖矩形范围之内的点,求最多可以覆盖多少个点?(不同点的坐标可能重合,十字架中心点可以随意移动)

方法一(暴力求解)

枚举矩形范围内的所有点,作为十字架的中心点,然后依照题意统计当前点作为十字架的中心点后能够覆盖多少个点,最后答案取最大值即可。

class Solution {
public:
    int cnt[200005];
    int g[200005][200005];
    int BoomKill(int n, int m, vector<Point>& Player) {
        for(int i=0;i<Player.size();i++){
            cnt[Player[i].x]++;//统计每一列(此处的列按照数学坐标系的来)玩家个数
            g[Player[i].x][Player[i].y]++;
        }
        int ans=0;
        for(int x=1;x<=n;x++){
            for(int y=1;y<=n;y++){
                int res=0;
                for(int k=max(1,x-m+1);k<=min(n,x+m-1);k++){
                    res+=g[k][y];
                }
                res+=cnt[x];
                res-=g[x][y];//十字架中心点的玩家数量加了两次,所以要减掉一次
                ans=max(ans,res);
            }
        }
        return ans;
    }
};

时间复杂度:图片说明 ,枚举中心点坐标是图片说明 的,暴力计算炸掉多少是图片说明 的,故总的时间复杂度为图片说明
空间复杂度:图片说明 ,保存矩形范围需要一个图片说明 的二维数组

方法二(正解)

前置知识:线段树,二分查找,排序

(注意,以下"行"和"列"是数学坐标系下的,而不是二维数组中的)
既然可以炸掉一整列,我们暂不考虑这个条件,首先考虑行范围的限制。那么能炸的范围就是一个宽度最多为图片说明 ,高度为图片说明 的框,如下图所示。
图片说明
其中中间的那条红线表示当前十字架的中心线,左右两条红线为能够覆盖的宽度范围(宽度最多为图片说明 )。
考虑已知当前这个框,怎么求解答案?
当前已经固定了横坐标(十字架中心线的横坐标),记为图片说明 ,考虑枚举中心线上的图片说明 个点,分别求出以这图片说明 个点向左右扩展能够炸掉的玩家数量,求出最大值即可,最大值记为图片说明
图片说明 表示横坐标为图片说明 的这一列玩家总数量。
那么当前中心线下的答案为图片说明 ,最终答案取所有中心线答案最大值即可。
其中图片说明 可以预先统计出来,求解图片说明 可以利用线段树求解,显然这里是单点修改,区间求最大值
那么我们考虑如何求解(十字架中心点的玩家数量)
方法一:对玩家以图片说明 作为第一关键字,图片说明 作为第二关键字排序,在给定图片说明 范围内对图片说明 做二分查找即可。
方法二:图片说明 个动态开点线段树,在线段树内做"单点查询"
这边我们采用方法一
具体的:
1.首先将玩家以图片说明 为第一关键字,图片说明 为第二关键字排序。
2.
(1)主循环枚举方框中心线横坐标,求出方框左边界图片说明 和右边界图片说明 ,将横坐标小于等于图片说明 的玩家"加入"(线段树单点修改)
(2)计算当前中心线对答案的贡献,取max。
(3)计算下一次循环的图片说明 ,把横坐标小于图片说明 的玩家全部"踢出"(线段树单点修改)
3.输出答案

bool cmp(const Point& x,const Point& y){
    if(x.x==y.x)return x.y<y.y;//如果x相同时,按照y从小到大排序
    return x.x<y.x;//按照x从小到大排序
}
class Solution {
public:
    struct segtree{
        int mxval[200005<<2][2];
        //mxval[x][0] 表示线段树节点x所管辖区间的最大值
        //mxval[x][1] 表示线段树节点x所管辖区间的最大值所对应的下标
        inline int lson(int x){
            return x<<1;
        }
        inline int rson(int x){
            return x<<1|1;
        }
        inline void pushup(int x){
            if(mxval[lson(x)][0]>mxval[rson(x)][0]){
                mxval[x][0]=mxval[lson(x)][0];
                mxval[x][1]=mxval[lson(x)][1];
            }else{
                mxval[x][0]=mxval[rson(x)][0];
                mxval[x][1]=mxval[rson(x)][1];
            }
        }
        void add(int x,int l,int r,int p,int v){//单点修改
            if(l==r){
                mxval[x][0]+=v;
                mxval[x][1]=p;
                return;
            }
            int mid=(l+r)>>1;
            if(p<=mid)add(lson(x),l,mid,p,v);
            else add(rson(x),mid+1,r,p,v);
            pushup(x);
        }
    }seg;
    int st[200005],ed[200005],cnt[200005];
    void clear(){
        //多测得清空
        memset(st,0,sizeof st);
        memset(ed,-1,sizeof ed);
        memset(cnt,0,sizeof cnt);
        memset(seg.mxval,0,sizeof seg.mxval);
    }
    inline int find_player_cnt(vector<Point>& p,int x,int y){
        //查询坐标为(x,y)的玩家的数量
        int ansl=0,ansr=-1;
        int l=st[x],r=ed[x];
        while(l<=r){//第一次二分,寻找该坐标最先出现的位置
            int mid=(l+r)>>1;
            if(p[mid].y==y){
                ansl=mid;
                r=mid-1;
            }else if(p[mid].y<y){
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        l=st[x],r=ed[x];
        while(l<=r){//第二次二分,寻找该坐标最后出现的位置
            int mid=(l+r)>>1;
            if(p[mid].y==y){
                ansr=mid;
                l=mid+1;
            }else if(p[mid].y<y){
                l=mid+1;
            }else{
                r=mid-1;
            }
        }
        return ansr-ansl+1;
    }
    int BoomKill(int n, int m, vector<Point>& Player) {
        clear();
        sort(Player.begin(),Player.end(),cmp);//先排序
        //st[x]表示横坐标为x的玩家第一次出现的位置
        st[Player[0].x]=0;
        //ed[x]表示横坐标为x的玩家最后一次出现的位置
        ed[Player[0].x]=0;
        cnt[Player[0].x]++;
        for(int i=1;i<Player.size();i++){
            if(Player[i].x!=Player[i-1].x){
                st[Player[i].x]=i;
                ed[Player[i].x]=i;
            }else{
                ed[Player[i].x]=i;
            }
            cnt[Player[i].x]++;
        }
        int ans=0;
        int idx=0;
        for(int cx=1;cx<=n;cx++){
            int lx=max(1,cx-m+1);
            int rx=min(n,cx+m-1);
            while(idx<Player.size()&&Player[idx].x<=rx){
                seg.add(1,1,n,Player[idx].y,1);//加入玩家
                idx++;
            }
            ans=max(ans,cnt[cx]+seg.mxval[1][0]-find_player_cnt(Player,cx,seg.mxval[1][1]));
            if(max(1,cx+1-m+1)!=lx){
                for(int i=st[lx];i<=ed[lx];i++){
                    seg.add(1,1,n,Player[i].y,-1);//踢出玩家
                }
            }
        }
        return ans;
    }
};

时间复杂度: 线段树单点修改,查询还有二分查找都是图片说明 的,由于每个玩家只会进出线段树一次,所以复杂度为图片说明
空间复杂度:图片说明 ,线段树开四倍内存是图片说明 的,程序中其他数组也是图片说明 级别的