题目:
n,r,c<=3000
分析:先枚举左边界 然后将点从高到矮连链表 再统计从每一个点开始含括k个点的矩形 能够上下延伸得到的多少个矩形。
然后枚举右边界删点 利用大矩形原有的信息修改后 去累加新的左右宽度较小的矩形的贡献。
这种算法的优势: 每一次缩小矩形的时候 可以不用重新计算小矩形的贡献 只需要通过大矩形原有的贡献进行修改
修改方式:通过枚举右边界缩小矩形 删掉超出右边界范围内的那一列的点
删点时重新统计贡献 :也就是对被影响的点重新计算一下(通过跳链表找到受影响的点)
#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 */