大致题意: 给定一个(r imes c)的矩阵,其中有(n)个(1)。问有多少子矩阵包含至少(k)个(1)。
解题思路
不得不说这题的思想是非常巧妙的。
有一个显而易见的(O(r^2c))暴力,即枚举子矩阵上下边界,然后用尺取法(双指针)扫一遍。
现在我们考虑仍旧枚举上边界,然后从下往上枚举下边界,每次移动下边界时求出删去这一层点给答案带来的变化。
删去一个点,因此而减少的贡献就是所有包含它所在列且恰好包含(k)个点的矩阵个数。
我们用链表来维护有点的每一列,那么暴力找出这些矩阵最多也就只要往前跳(k)次,同时维护好右边界即可。
总复杂度(O(r^2k))。
(P.S.) 写完后发现似乎也可以从上往下枚举下边界,然后加上每一层点的答案?而且这样还不用求初始答案了。。。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 3000
#define K 10
#define LL long long
using namespace std;
int r,c,n,k,cnt[N+5],pre[N+5],nxt[N+5],C[N+5],v[N+5][N+5];struct P {int x,y;}p[N+5];
int main()
{
RI i,j,s,x,y;for(cin>>r>>c>>n>>k,i=1;i<=n;++i) cin>>x>>y,v[x][++C[x]]=y;//记下每一行的点的列号
RI t,cur,res;LL ans=0;for(i=1;i<=r;++i)//枚举上边界
{
for(res=0,j=i;j<=r;++j) for(s=1;s<=C[j];++s) ++cnt[v[j][s]];//枚举所有点计算每一列的点数
for(pre[1]=0,j=2;j<=c+1;++j) pre[j]=cnt[j-1]?j-1:pre[j-1];//链表初始化向前指针
for(nxt[c]=c+1,j=c-1;~j;--j) nxt[j]=cnt[j+1]?j+1:nxt[j+1];//链表初始化向后指针
for(x=nxt[0];x<=c;x=nxt[x])//枚举子矩阵第一个有1的列,计算初始答案
{
t=0,y=x;W(y<=c&&(t+=cnt[y])<k) y=nxt[y];//暴力向后跳到第一个满足条件的右边界
res+=(x-pre[x])*(c-y+1);//统计答案
}
for(j=r;j>=i;--j) for(ans+=res,s=1;s<=C[j];++s)//从下往上枚举下边界,删去每层点的贡献
{
if(--cnt[cur=v[j][s]]>=k) continue;//如果这一列的点数仍旧大于等于k,对答案无影响
t=0,y=cur;W(y<=c&&(t+=cnt[y])<k-1) y=nxt[y];//暴力向后跳找到初始右边界
x=cur;W(x)//枚举左边界
{
W(y^cur&&t-cnt[y]>=k-1) t-=cnt[y],y=pre[y];if(y==cur&&t>=k) break;//右边界能移就移,若不合法就结束循环
t+1==k&&(res-=(x-pre[x])*(nxt[y]-y)),t+=cnt[x=pre[x]];//恰好k个点时从答案中减去贡献,然后移动左边界
}
!cnt[cur]&&(nxt[pre[cur]]=nxt[cur],pre[nxt[cur]]=pre[cur]);//没点后从链表中删去
}
}return printf("%lld
",ans),0;//输出答案
}