[bzoj2639]矩形计算——二维莫队or四维数点

数点 同时被 2 个专栏收录
0 篇文章 0 订阅
5 篇文章 0 订阅

题目大意:

输入一个n*m的矩阵,矩阵的每一个元素都是一个整数,然后有q个询问,每次询问一个子矩阵的权值。矩阵的权值是这样定义的,对于一个整数x,如果它在该矩阵中出现了p次,那么它给该矩阵的权值就贡献 p 2 p^2 p2

思路:

二维莫队的模板题,但是复杂度好像不太明确。。。
于是可以换一种思路,考虑平方的几何意义,即两个颜色相同的点可以构成一个点对,每一次询问在某矩阵中出现的点对个数。
对于每一个矩阵我们拆成(x1,y1,x2,y2),每一个点对同样也是(x1’,y1’,x2’,y2’),即每次询问 x 1 ′ ≥ x 1 , y 1 ′ ≥ y 1 , x 2 ′ ≤ x 2 , y 2 ′ ≤ y 2 x_1'\geq x_1,y_1'\geq y_1,x_2'\leq x_2,y_2'\leq y_2 x1x1,y1y1,x2x2,y2y2的点对个数,可以离线一维之后用三维树状数组即可。
但是上面的做法复杂度会有问题,对于出现次数很多的颜色,它缩能够构成的点对就有很多,但是这种颜色本身必定也很少,于是我们只要对于颜色的出现次数分治一下就好了,对于出现次数较多的颜色直接用二维前缀和来维护。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
#define y1 fuckyou
#define pii pair<int,int>
#define fi first
#define se second
#define mk make_pair
typedef long long ll;

using namespace std;

void File(){
	freopen("bzoj2639.in","r",stdin);
	freopen("bzoj2639.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

const int maxn=200+10;
const int maxm=1e5+10;
int n,m,q,a[maxn][maxn],b[maxn*maxn],tot,ans[maxm];
vector<pii>pos[maxn*maxn];
int lim=100,sum[maxn<<1][maxn][maxn],cnt,vis[maxn*maxn];

struct BIT{
	int s[maxn<<1][maxn][maxn];
	int lowbit(int x){return x&(-x);}
	void add(int x,int y,int z){
		for(int i=x;i<=m;i+=lowbit(i))
			for(int j=y;j<=n;j+=lowbit(j))
				for(int k=z;k<=m;k+=lowbit(k))
					++s[i][j][k];
	}
	int query(int x,int y,int z){
		int ret=0;
		for(int i=x;i>=1;i-=lowbit(i))
			for(int j=y;j>=1;j-=lowbit(j))
				for(int k=z;k>=1;k-=lowbit(k))
					ret+=s[i][j][k];
		return ret;
	}
}T;

void init(){
	read(n); read(m); read(q);
	REP(i,1,n)REP(j,1,m)read(a[i][j]),b[++tot]=a[i][j];
	sort(b+1,b+tot+1);
	tot=unique(b+1,b+tot+1)-b-1;
	REP(i,1,n)REP(j,1,m)a[i][j]=lower_bound(b+1,b+tot+1,a[i][j])-b;
	REP(i,1,n)REP(j,1,m)pos[a[i][j]].push_back(mk(i,j));
	REP(i,1,n)REP(j,1,m)if(!vis[a[i][j]] && (int)pos[a[i][j]].size()>lim){
		vis[a[i][j]]=1;
		++cnt;
		REP(f,1,n)REP(g,1,m)
			sum[cnt][f][g]=sum[cnt][f-1][g]+sum[cnt][f][g-1]-sum[cnt][f-1][g-1]+(a[f][g]==a[i][j]);
		pos[a[i][j]].clear();
	}
}

int cnt_n;

struct node{
	int x1,y1,x2,y2,id;
	bool operator < (const node & tt) const {
		return x1>tt.x1;
	}
}ask[maxm],nod[maxn*2*maxn*maxn];

void work(){
	REP(i,1,n)REP(j,1,m)if(!vis[a[i][j]]){
		int id=a[i][j];
		vis[id]=1;
		//cout<<id<<endl;
		REP(f,0,pos[id].size()-1){
			REP(g,0,pos[id].size()-1){
				int x1=min(pos[id][f].fi,pos[id][g].fi);
				int x2=max(pos[id][f].fi,pos[id][g].fi);
				int y1=min(pos[id][f].se,pos[id][g].se);
				int y2=max(pos[id][f].se,pos[id][g].se);
				//cout<<x1<<" "<<y1<<" "<<x2<<" "<<y2<<endl;
				nod[++cnt_n]=(node){x1,y1,x2,y2,0};
			}
		}
		//cout<<"------------"<<endl;
	}
	sort(nod+1,nod+cnt_n+1);
	REP(i,1,q)read(ask[i].x1),read(ask[i].y1),read(ask[i].x2),read(ask[i].y2),ask[i].id=i;
	sort(ask+1,ask+q+1);
	int p=1;
	REP(i,1,q){
		while(p<=cnt_n && nod[p].x1>=ask[i].x1)T.add(m-nod[p].y1+1,nod[p].x2,nod[p].y2),++p;
		int x1=ask[i].x1,y1=ask[i].y1,x2=ask[i].x2,y2=ask[i].y2;
		ans[ask[i].id]+=T.query(m-y1+1,x2,y2);
		REP(j,1,cnt)ans[ask[i].id]+=
			(sum[j][x2][y2]-sum[j][x1-1][y2]-sum[j][x2][y1-1]+sum[j][x1-1][y1-1])
			*(sum[j][x2][y2]-sum[j][x1-1][y2]-sum[j][x2][y1-1]+sum[j][x1-1][y1-1]);
		//cout<<x1<<" "<<y1<<" "<<x2<<" "<<y2<<endl;
	}
	REP(i,1,q)printf("%d\n",ans[i]);
}

int main(){
	File();
	init();
	work();
	cerr<<(double)clock()/CLOCKS_PER_SEC<<endl;
	return 0;
}

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:编程工作室 设计师:CSDN官方博客 返回首页

打赏作者

ylsoi

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值