题目:

n,r,c<=3000

分析:先枚举左边界   然后将点从高到矮连链表   再统计从每一个点开始含括k个点的矩形   能够上下延伸得到的多少个矩形。
然后枚举右边界删点    利用大矩形原有的信息修改后    去累加新的左右宽度较小的矩形的贡献。

这种算法的优势: 每一次缩小矩形的时候 可以不用重新计算小矩形的贡献 只需要通过大矩形原有的贡献进行修改
修改方式:通过枚举右边界缩小矩形 删掉超出右边界范围内的那一列的点
删点时重新统计贡献 :也就是对被影响的点重新计算一下(通过跳链表找到受影响的点)

ys的图与详解

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define N 4005
int zong[N],x[N],y[N],r,c,n,k,q[N],val[N],nex[N],to[N],pre[N],num[N];
ll sum,ans=0;
vector<int>v[N];
bool cmp(int a,int b)
{
    if(q[a]==q[b]) return zong[a]<zong[b];
    return q[a]<q[b];
}
void del(int t)
{
    nex[pre[t]]=nex[t];//通过链表连边的方式删去t点 
    pre[nex[t]]=pre[t];
    sum-=val[t];//删掉这个点 就应该把其贡献去掉 
    int x=nex[t],y=nex[t];//跳nex是因为影响了下面一个点 因为下面一个点的上界是通过t来算的 t变了  
    for(int i=1;i<=k-1;i++) y=nex[y];
    for(int i=1;i<=k+1;i++){
        int v=(q[x]-q[pre[x]])*(r-q[y]+1);
        //这个点原来的val就是这个点向右包含恰好k个点 然后向上下延伸得到的矩形个数 
        sum+=v-val[x];// val贡献也应随着改变 所以应该重新统计一下x这个点的贡献 
        val[x]=v;
        x=pre[x],y=pre[y];//因为t被删去了 所以前面通过t这个点向右延伸达到k个的val值都应该改变 
    }
}
int main()
{
    freopen("baritone.in", "r", stdin);
    freopen("baritone.out", "w", stdout);
    int len=0;
    scanf("%d%d%d%d",&r,&c,&n,&k);
    for(int i=1;i<=n;i++) scanf("%d%d",&x[i],&y[i]),v[y[i]].push_back(i);
    for(int i=1;i<=c;i++){//枚举左边界 
        sum=0; len=0;
        for(int j=0;j<=20;j++)//防止越界 加入40个虚点 使得前面有20个点 后面有20个点 
         q[++len]=0,q[++len]=r+1;
        for(int j=1;j<=n;j++)
        if(y[j]>=i){//如果这个点在枚举的左边界之内 就放入队列 维护相关信息 
            q[++len]=x[j];//q是存的横坐标!! 
            zong[len]=y[j];
            to[j]=len;//to是在链表中的编号 
        }
        for(int j=1;j<=len;j++) num[j]=j;//num是每一个点的标号数组 便于对前len个在队列中的点 按横坐标为第一关键字排序 
        sort(num+1,num+1+len,cmp); 
        for(int j=1;j<=len-1;j++)
        nex[num[j]]=num[j+1],pre[num[j+1]]=num[j];//求每一个点的前驱和后继
        //for(int j=1;j<=len;j++) printf("%d ",pre[j]);
        nex[num[len]]=num[len];
        pre[num[1]]=num[1];
        for(int j=1;j<=len;j++){//接下来是答案的统计 
            int now=j;
            for(int p=1;p<=k-1;p++)//跳k-1次恰好跳到的点 使得j和now之间恰好有k个点(包含它们本身) j和now是上下关系 
            now=nex[now];
            val[j]=(q[j]-q[pre[j]])*(r-q[now]+1);
            //从pre[j]到 j表示 j可以向上延伸的长度 从q[now]到r是now向下可以延伸的长度 
            //根据乘法原理累计答案 +1是因为有一方可以不延伸
            sum+=val[j]; 
        } //printf("ans:%lld\n",ans);
        for(int j=c;j>=i;j--){
            ans+=sum;
            for(int p=0;p<v[j].size();p++)//右边界向左移动 删去在这一列的所有点 因为下一次它们就超出范围了
            del(to[v[j][p]]);//找到第j列的所有点在链表中的编号 然后删掉 更新答案 
        }
        //当右边界向左边界延伸的时候 其实是为了得到左右宽度更小的矩形答案 
        
    }
    printf("%lld\n",ans);
}
/*
3 2 3 3
1 1 
3 1 
2 2 
*/

 

posted on 2019-08-24 20:00  rua-rua-rua  阅读(167)  评论(0编辑  收藏  举报