把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【CF627E】Orchestra(尺取法+链表)

点此看题面

大致题意: 给定一个\(r\times 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\n",ans),0;//输出答案
}
posted @ 2020-08-21 18:55  TheLostWeak  阅读(194)  评论(0编辑  收藏  举报