题目链接传送门
这道题上来应该是找规律(打表不现实,也很浪费空间,就算允许也应该先考虑规律),这个规律也比较简单,就是按照主对角线对矩阵进行拆分,这样可以发现所有元素都呈现如下规律
ll ind(ll x,ll y,ll n){
ll qs=n/2,q=min(n-y+1,min(n-x+1,min(x,y)))-1;
if(x==qs+1&&y==qs+1){
return n*n;
}
ll ans=q*(8*qs+8*(qs-q+1))/2;
if(n-x==q){
ans+=n-q-y+1;
}else if(y - 1 == q){
ans += n - 2 * q + 1 + n - q - 1 - x;
}else if(x - 1 == q){
ans += n - 2 * q + 1 + n - 2 * q - 2 + y - q - 1;
}else{
ans += n - 2 * q + 1 + n - 2 * q - 2 + n - 2 * q - 1 + x - q - 1;
}
return ans;
}
接下来来到本题难点,就是我们要对每次询问O(1)地进行回答。这里可能有的同学可以通过一些RMQ问题想到可以拆分询问,将原点到询问矩形的四个区间通过加加减减的方式可以维护矩形的值,这也就是本题的思想通过树状数组来维护区间前缀和。
先放下维护前缀和的部分不谈,我们先来了解下树状数组。树状数组本身就是一个数组,只是我们使用一个特殊的拆分方法将它强行长成一棵树。它比线段树功能弱但实现起来方便~~(其实本题用线段树写代码好像要少点,但我不会嵌套,以后会补,大概?)~~
维护区间和的方法就如下啦(最大最小值相信你们会了)
ll lowbit(ll x){//长成一棵树的方法
return x&(-x);
}
void add(ll x,ll val){//(母猪)上树
if(x==0){//干死(1,1)
return ;
}
while(x<Max){
power[x]+=val;
x+=lowbit(x);
}
}
ll ask(ll x){
ll res=0;
while(x){
res+=power[x];
x-=lowbit(x);
}
return res;
}//这两个函数就是树状数组维护区间前缀和的模板了
ll lowbit(ll x){//长成一棵树的方法
return x&(-x);
}
void add(ll x,ll val){//(母猪)上树
if(x==0){//干死(1,1)
return ;
}
while(x<Max){
power[x]+=val;
x+=lowbit(x);
}
}
ll ask(ll x){
ll res=0;
while(x){
res+=power[x];
x-=lowbit(x);
}
return res;
}//这两个函数就是树状数组维护区间前缀和的模板了
解决了这个,我们可以开始解决如何O(1)了。其实能做到的方法只有一个呗:离线。这样我们可以使用附带滚动的方法滚完另一个数组,节省时间复杂度。那么我们选择哪个进行伴随滚动呢?其实轮不到我们选——凡是有包含关系的题目一定是被包含方伴随滚动。接下来就是怎么才能确定点在不在矩形范围能呢?(当然,你不能用计算几何的方法,因为那样滚不起来)。其实就是一个压缩的方法,然后好看,我们就选择压缩y轴滚动x轴。(这里可能有点难理解为什么可以压缩,因为这里是一棵==参天大“树”==,可以当成一种固定方法先记下来)这样我们就稳稳地求出前缀和了~~(还是二维的)~~
void solve(){
int pos=1;
for(int i=1;i<=tot;i++){
while(pos<=m&&point[pos].y<=qujian[i].y){
add(point[pos].x,point[pos].z);
pos++;
}
ans[qujian[i].id]+=ask(qujian[i].x)*qujian[i].flag;
}
}
void solve(){
int pos=1;
for(int i=1;i<=tot;i++){
while(pos<=m&&point[pos].y<=qujian[i].y){
add(point[pos].x,point[pos].z);
pos++;
}
ans[qujian[i].id]+=ask(qujian[i].x)*qujian[i].flag;
}
}
接下来的事情就很无聊了,补一下输入输出,控制下格式,算下数据范围(这里要用long long 想必大家都明白)。
那为什么这么无聊我还要说呢?
当然是因为:就算这里你使用了==ios::sync_with_stdio(false);==最后你用cout输出也能把你T了,我也不知道为什么,反正就是T,de了2小时bug,爽!!!!
下面就是完全代码了
#include
#define ll long long
using namespace std;
const int Max=1e6+10;
ll ans[Max];//储存答案
ll power[Max*2];//树状数组
int tot;//总的区间数
int n,m,q;
struct p{
p(int a=0,int b=0,int c=0) {
x=a;y=b;z=c;
}
ll x,y,z;//z为权值
bool operator <(const p n)const{
return y<n.y;//这里是y方向压缩,x亦可
}
}point[Max];
struct qu{
qu(int a=0,int b=0,int c=0,int d=0){
x=a;y=b;id=c;flag=d;
}
ll x,y,id;//id为序号,离线使用
ll flag;//看他的贡献
bool operator <(const qu n)const{
return y<n.y;//压缩方向要一致
}
}qujian[4*Max];//不要笑我英语不好
ll lowbit(ll x){
return x&(-x);
}
void add(ll x,ll val){//(母猪)上树
if(x==0){//干死(1,1)
return ;
}
while(x<Max){
power[x]+=val;
x+=lowbit(x);
}
}
ll ask(ll x){
ll res=0;
while(x){
res+=power[x];
x-=lowbit(x);
}
return res;
}//这两个函数就是树状数组维护区间前缀和的模板了
ll chai(ll x) {
ll sum = 0;
while (x) {
sum += x % 10;
x /= 10;
}
return sum;
}
void solve(){
int pos=1;
for(int i=1;i<=tot;i++){
while(pos<=m&&point[pos].y<=qujian[i].y){
add(point[pos].x,point[pos].z);
pos++;
}
ans[qujian[i].id]+=ask(qujian[i].x)*qujian[i].flag;
}
}
ll ind(ll x,ll y,ll n){
ll qs=n/2,q=min(n-y+1,min(n-x+1,min(x,y)))-1;
if(x==qs+1&&y==qs+1){
return n*n;
}
ll ans=q*(8*qs+8*(qs-q+1))/2;
if(n-x==q){
ans+=n-q-y+1;
}else if(y - 1 == q){
ans += n - 2 * q + 1 + n - q - 1 - x;
}else if(x - 1 == q){
ans += n - 2 * q + 1 + n - 2 * q - 2 + y - q - 1;
}else{
ans += n - 2 * q + 1 + n - 2 * q - 2 + n - 2 * q - 1 + x - q - 1;
}
return ans;
}
int main(){
ios::sync_with_stdio(false);
int t;
cin>>t;
while(t--){
cin>>n>>m>>q;
for(int i=0;i<=n+n;i++){
power[i]=0;
}
for(int i=0;i<=q;i++){
ans[i]=0;
}
tot=0;
ll x,y;
ll x1,y1,x2,y2;
for(int i=1;i<=m;i++){
cin>>x>>y;
ll val=ind(x,y,n);
val=chai(val);
point[i] = p{x,y,val};
}
sort(point+1,point+1+m);
for(int i=1;i<=q;i++){
cin>>x1>>y1>>x2>>y2;
qujian[++tot]=qu(x1-1,y1-1,i,1);
qujian[++tot]=qu(x1-1,y2,i,-1);
qujian[++tot]=qu(x2,y1-1,i,-1);
qujian[++tot]=qu(x2,y2,i,1);
}
sort(qujian+1,qujian+1+tot);
solve();
for(int i=1;i<=q;i++){
printf("%lld\n",max((ll)0,ans[i]));
}
}
return 0;
}