1. 程式人生 > 實用技巧 >【CF627E】Orchestra(連結串列)

【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;//輸出答案
}