展开
题目描述
Paul is at the orchestra. The string section is arranged in an r×cr×c rectangular grid and is filled with violinists with the exception of nn violists. Paul really likes violas, so he would like to take a picture including at least kk of them. Paul can take a picture of any axis-parallel rectangle in the orchestra. Count the number of possible pictures that Paul can take.
Two pictures are considered to be different if the coordinates of corresponding rectangles are different.
输入格式
The first line of input contains four space-separated integers rr , cc , nn , kk ( 1<=r,c,n<=30001<=r,c,n<=3000 , 1<=k<=min(n,10)1<=k<=min(n,10) ) — the number of rows and columns of the string section, the total number of violas, and the minimum number of violas Paul would like in his photograph, respectively.
The next nn lines each contain two integers x_{i}x
i
and y_{i}y
i
( 1<=x_{i}<=r1<=x
i
<=r , 1<=y_{i}<=c1<=y
i
<=c ): the position of the ii -th viola. It is guaranteed that no location appears more than once in the input.
输出格式
Print a single integer — the number of photographs Paul can take which include at least kk violas.
题意翻译
在一个 r \times cr×c 的矩阵中有 nn 个点,问有多少个连续子矩阵至少包含 kk 个点。
r,c,n \le 3 \times 10^3r,c,n≤3×10
3
,k \le 10k≤10。
输入输出样例
输入 #1复制
2 2 1 1
1 2
输出 #1复制
4
输入 #2复制
3 2 3 3
1 1
3 1
2 2
输出 #2复制
1
输入 #3复制
3 2 3 2
1 1
3 1
2 2
输出 #3复制
4
说明/提示
We will use ‘*’ to denote violinists and ‘#’ to denote violists.
In the first sample, the orchestra looks as follows:
*#
**
Paul can take a photograph of just the viola, the 1×21×2 column containing the viola, the 2×12×1 row containing the viola, or the entire string section, for 44 pictures total.In the second sample, the orchestra looks as follows:
#
#
#*
Paul must take a photograph of the entire section.In the third sample, the orchestra looks the same as in the second sample.
首先考虑最暴力的解法应该就是枚举上下边界然后用单调栈去求了,复杂度O(n^3)
考虑怎么求优化这个过程,考虑能不能只枚举上边界和下边界然后不用单调栈。
发现给定的点数很少(跟n,mn,m同阶),于是考虑从这里下手。
考虑枚举一个上边界ii,然后考虑将上边界为i,下边界为jj的合法矩形个数算出来。
考虑怎么存它,且还要动态修改(因为要把下边界从n往上移)。
一个上边界的点,找到在它左边的第kk个在ii到nn行之间的给定的点,那么它的贡献就是这个点的纵坐标。
于是考虑将第ii行到第nn行的点提取出来按照纵坐标排个序按照顺序建成一个双向链表,在对每个点记一个cnti
表示上边界有多少个点的左边第kk个点是i。
然后考虑一行一行地从下面往上面删行,先考虑删除一个点。
画个图就可以发现,删除一个点实际上就是把它到左边的kk个点集体左移了一位,贡献的减少量也可以非常简单的通过双向链表来实现,同时维护一下cnt的变化,然后将这个点删掉即可。
于是运用双向链表可以非常简单的用O(k)的时间来删除一个点。
于是用这样的方法我们可以用上一行再删去这一行的点求得这一行的答案。
这样子一行行删去即可。
#include
#define ll long long
using namespace std;
const int N = 3077;
int n,m,p,k;
int s[N],t[N],last[N],nxt[N];
ll ans;
vector <int> dot[N];
int main()
{
scanf("%d%d%d%d",&n,&m,&p,&k);
for (int i=1;i<=p;++i){
int x,y;
scanf("%d%d",&x,&y);
dot[x].push_back(y);
}
for (int u=n;u>=1;--u){
for (int i=0;i<dot[u].size();++i) ++s[dot[u][i]];
int l=1,r=0,num=0;
ll inc=0;
while (true){
while (r+1<=m&&num<k) num+=s[++r];
if (num<k) break;
while (l<=r&&num>=k) num-=s[l++],inc+=m-r+1;//inc 合法矩阵个数
}
for (int i=1;i<=m;++i) t[i]=s[i];
for (int i=0;i<=m+1;++i) last[i]=i-1,nxt[i]=i+1;
for (int i=1;i<=m;++i)
if (!s[i]) last[nxt[i]]=last[i],nxt[last[i]]=nxt[i];
for (int d=n;d>=u;--d){
ans+=inc;
for (int i=0;i<dot[d].size();++i){
int cur=dot[d][i],sl=t[cur],sr=0;
l=r=cur;
while (nxt[r]<=m&&sl+sr+t[nxt[r]]<=k) sr+=t[r=nxt[r]];
while (true){
if (sl+sr==k) inc-=(l-last[l])*(nxt[r]-r);
sl+=t[l=last[l]];
if (!l||sl>k) break;
while (sl+sr>k) sr-=t[r],r=last[r];
}
--t[cur];
if (!t[cur]) last[nxt[cur]]=last[cur],nxt[last[cur]]=nxt[cur];
}
}
}
printf("%lld\n",ans);
return 0;
}