树状数组扫描线维护矩形区域和 ICPC2019南京站网络赛

题目链接:https://nanti.jisuanke.com/t/41298

题意:给你一个n*n的矩阵(n为1e6),矩阵上的数具有一定规律(给定n后每个位置元素值都已确定,是一个内螺旋矩阵)。之后会加点,被加的点其上的值才会被启用,未被启用的点的值为0,先p次询问,每次询问一个矩形区域内值的和为多少。

分析:螺旋矩阵找规律可以分成一圈圈的来看,一个n*n的矩阵,那么最多就有n/2个圈,我们先预处理出前i个圈所有的点的数目d[i],之后对每个当前圈,都分为上下左右四侧来找即可。

该题便转化为一个求矩形区域和的问题,因为n太大了,二维前缀和明显不可以。

不仅如此,因为n特别大,我们还必须对其进行离散化。

我们可以把每次询问拆成四次询问,对于查询x1,y1,x2,y2,有

ans = map[x2][y2]− map[x2][y1−1]− map[x1−1][y2]+ map[x1−1][y1−1]

考虑二维降低位,将其按x轴排序
x坐标排序后,我们进行前缀和操作的时候就不需要考虑x坐标了(因为排序后一定有后面来的点的x坐标一定比前面所有的x坐标都大,故当这个点为询问时只需要判断比该点的yy坐标小的点有多少个即可。),然后同时将y坐标离散化掉

另外,有一个非常相似的题:洛谷P2163 不过这个题数据范围更大,还这样写会T,就分别存储横纵坐标,很值得一看,讲解在这份答案后面。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000010;
int T;
int n,m,p;
ll d[N],c[N];
struct node{
    int x1,y1,x2,y2;
};
vector<node> qu;//查询
struct Node{
    int x,y;
    int type;//type为0表示添加新点
}a[N];
bool cmp(Node a,Node b){
    if(a.x == b.x){
        if(a.y == b.y)return a.type == 0;
        return a.y < b.y;
    }
    return a.x < b.x;
}
//vector<int> v;//离散y
int b[N];
int num;
void add(int x,int y){ for(;x<=num;x+=x&-x)c[x] += y; } ll ask(int x){ ll res = 0; for(;x;x-=x&-x)res += c[x]; return res; } map<int,map<int,int>> mp; int id[N]; ll get(int x,int y){ int cx = n / 2 + 1; int cy = n / 2 + 1;//(cx,cy)即中心坐标 int k = max(abs(x-cx),abs(y-cy));//k表示在第几圈 if(k == 0)return 1ll * n * n; ll res = d[k-1];//圈内有多少个 //分4个case计算 if(y-cy == k && x < cx + k){//在上层 res += cx + k - x; }else if(cx - x == k && y < cy + k){//在左侧 res += k * 2 + cy + k - y; }else if(cy - y == k && x > cx - k){//在下层 res += k * 4 + x - (cx - k); }else if(x - cx == k){//在右侧 res += k * 6 + y - (cy - k); } res = 1ll * n * n - res;//最后倒过来,因为上面是按照中心为1算的 return res + 1; } int calc(ll x){ int res= 0; while(x){res += x % 10;x /= 10;} return res; } int main(){ scanf("%d",&T); d[0] = 1;d[1] = 8; for(int i=2;i<N/2;i++)d[i] = d[i-1] + 8; for(int i=1;i<N/2;i++)d[i] += d[i-1];//计算i圈以内有多少个点 while(T--){ mp.clear();qu.clear();//v.clear(); memset(c,0,sizeof c); scanf("%d%d%d",&n,&m,&p); for(int i=1;i<=m;i++){ int x,y; scanf("%d%d",&x,&y); a[i] = {x,y,0}; } for(int i=1;i<=p;i++){ int x1,x2,y1,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2); qu.push_back({x1,y1,x2,y2}); a[++m] = {x1-1,y1-1,1}; a[++m] = {x2,y2,1}; a[++m] = {x1-1,y2,1}; a[++m] = {x2,y1-1,1}; } //此时m的数量是所有要加的点和要查询的点的总和 for(int i=1;i<=m;i++){ b[i]=a[i].y; } sort(b+1,b+1+m); //v.erase(unique(v.begin(),v.end()),v.end()); num=unique(b+1,b+1+m)-b-1; sort(a+1,a+m+1,cmp); for(int i=1;i<=m;i++){ id[i] = lower_bound(b+1,b+1+num,a[i].y) - b; } for(int i=1;i<=m;i++){ if(a[i].type == 0)add(id[i],calc(get(a[i].x,a[i].y))); else if(a[i].type == 1)mp[a[i].x][a[i].y] = ask(id[i]); } for(int i=0;i<p;i++){ int x1 = qu[i].x1,x2 = qu[i].x2,y1 = qu[i].y1,y2 = qu[i].y2; ll res = mp[x2][y2] + mp[x1-1][y1-1] - mp[x1-1][y2] - mp[x2][y1-1]; printf("%lld\n",res); } } return 0; }

 接下来是洛谷P2163这道题

题意是花园里每个数可以用一个整数坐标来表示,多次询问,每次询问一个矩形区域里有多少个树

离散化这方面我们不再是全部都一起离散化了,而是横纵坐标分别自己离散化,这样可以减少最后的N

然后我们用了两个存储查询的向量数组Q和q,存储的都是当前询问的下标,并且向量下标分别是左下角的横坐标和右上角的横坐标

我们知道是按横坐标排序的,我们要求的答案就是F(x2,y2)−F(x1−1,y2)−F(x2,y1−1)+F(x1−1,y1−1)

之后加减什么的自己体会吧

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<queue>
using namespace std;
const int MAXN=500004;
struct node{int x,y,xx,yy;}a[MAXN];vector<int>V[MAXN],Q[MAXN],q[MAXN];
int n,m,N,M,sum[MAXN],x[MAXN],y[MAXN],tmpx[MAXN*3],tmpy[MAXN*3],totx,toty,ans[MAXN];
void modify(int x){for(;x<=M;x+=x&-x)sum[x]++;}
int query(int x){int ans=0;for(;x;x-=x&-x) ans+=sum[x];return ans;}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]),tmpx[++totx]=x[i],tmpy[++toty]=y[i];
    for(int i=1;i<=m;i++) {
        scanf("%d%d%d%d",&a[i].x,&a[i].y,&a[i].xx,&a[i].yy);a[i].x--;a[i].y--;
        tmpx[++totx]=a[i].x;tmpx[++totx]=a[i].xx;tmpy[++toty]=a[i].y;tmpy[++toty]=a[i].yy;
    }
    sort(tmpx+1,tmpx+totx+1);sort(tmpy+1,tmpy+toty+1);
    N=unique(tmpx+1,tmpx+totx+1)-tmpx-1;M=unique(tmpy+1,tmpy+toty+1)-tmpy-1;
    for(int i=1;i<=n;i++){
        x[i]=lower_bound(tmpx+1,tmpx+N+1,x[i])-tmpx;
        y[i]=lower_bound(tmpy+1,tmpy+M+1,y[i])-tmpy;
        V[x[i]].push_back(y[i]);
    }
    for(int i=1;i<=m;i++){
        a[i].x=lower_bound(tmpx+1,tmpx+N+1,a[i].x)-tmpx;
        a[i].y=lower_bound(tmpy+1,tmpy+M+1,a[i].y)-tmpy;
        a[i].xx=lower_bound(tmpx+1,tmpx+N+1,a[i].xx)-tmpx;
        a[i].yy=lower_bound(tmpy+1,tmpy+M+1,a[i].yy)-tmpy;
        Q[a[i].x].push_back(i);q[a[i].xx].push_back(i);
    }
    for(int i=1;i<=N;i++){
        for(int j=0;j<V[i].size();j++) modify(V[i][j]);//添加的是纵坐标 
        //接下来两个答案的添加便是这个算法的核心了
        //我们是把一个询问拆成了四个询问 
        for(int j=0;j<Q[i].size();j++) ans[Q[i][j]]+=query(a[Q[i][j]].y)-query(a[Q[i][j]].yy);
        for(int j=0;j<q[i].size();j++) ans[q[i][j]]+=query(a[q[i][j]].yy)-query(a[q[i][j]].y);
    }
    for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}

 

posted @ 2019-09-02 19:51  清酒令  阅读(265)  评论(0编辑  收藏  举报