Description
\(r\times c\) 的矩阵里有 \(n\) 个点,询问至少包含 \(k\) 个点的子矩阵有多少个。 \(r,c,n\leq 3\times 10^3 , k\leq 10\)
Solution
容易想到一个 \(n^3\) 的做法。枚举上下边界,中间用 two-pointer 计算贡献。
但是没有用到 \(k\leq 10\) 这个性质。钦定一个点的贡献为使它成为矩阵最左边的点的合法矩阵个数,考虑对点进行增量更新,容易发现新增一个点只会对按横坐标排序后的往前数的 \(k\) 个点造成贡献。配合平衡树可以做到 \(O(n^2k\log n)\) 的复杂度。但这样不够优秀。瓶颈在于找下标,所以想到反过来操作,先排序后,用链表维护,然后只需要删除,这样就可以做到 \(O(n^2k)\)。注意边界细节。
#pragma GCC optimize(2)
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<cassert>
using namespace std;
typedef long long ll;
inline int read(){
int x=0,flag=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
return flag? x:-x;
}
const int N=3e3+7;
struct Node{
int x,y;
bool operator <(const Node &X) const{
return x>X.x;
}
}a[N];
vector<int> V[N];
int head,tail,l[N],r[N];
inline void Ins(int x){
l[x]=tail,r[tail]=x,tail=x;
}
int main(){
freopen("baritone.in","r",stdin);
freopen("baritone.out","w",stdout);
int r_=read(),c_=read(),
n=read(),k=read();
for(int i=1;i<=n;i++)
a[i].x=read(),a[i].y=read();
sort(a+1,a+1+n);
a[n+1]=(Node){0,0},a[0]=(Node){r_+1,0};
ll ans=0;
for(int L=1;L<=c_;L++){
head=tail=0; int cnt=0;
for(int i=1;i<=r_;i++) V[i].clear();
for(int i=1;i<=n;i++)
if(a[i].y>=L){
Ins(i),cnt++;
V[a[i].y].push_back(i);
}
if(cnt<k) break; Ins(n+1); ll ret=0;
int p=head; for(int i=1;i<=k;i++) p=r[p];
for(int i=r[head];p!=tail;i=r[i],p=r[p])
ret+=1ll*(a[l[i]].x-a[i].x)*a[p].x;
ans+=ret;
for(int R=c_;R>L;R--){
for(int j:V[R]){
cnt--;
int p=j; for(int i=1;i<k&&l[p]!=head;p=l[p],i++);
int o=p; for(int i=1;i<k&&p!=tail;i++,p=r[p]);
if(p==tail) continue;
for(;p!=tail&&l[o]!=j;p=r[p],o=r[o])
ret-=1ll*(a[l[o]].x-a[o].x)*(a[p].x-a[r[p]].x);
// if(p!=tail) ret-=1ll*(a[l[o]].x-a[o].x)*a[p].x;
r[l[j]]=r[j],l[r[j]]=l[j];
}
assert(ret>=0);
if(cnt<k) break; ans+=ret;
}
}
printf("%lld",ans);
}