1. 程式人生 > 其它 >k 倍子矩形(列舉暴力)

k 倍子矩形(列舉暴力)

差分/字首和思路的深度應用,其實似乎是優化列舉量的常見方法但是我卻不知道……

題目放在洛谷U188718,出處在 SDFZ-NOIP2021 模擬 11-T2。

顯然的暴力是 \(\mathcal{O(n^4)}\),預處理二維字首和,列舉左上角、右下角,期望得分 \(60\),具體因實現而異。

觀察資料範圍發現 \(n\le 400\),只需要優化到 \(\mathcal{O(n^3)}\) 即可。考慮 \(n^2\) 地列舉一個縱向區間 \([l,r]\),如下圖所示:

對於這個區間,用指標從左向右掃,記錄字首矩形面積 \(c\)。在已經預處理了縱向字首和的基礎上,這是 \(\mathcal{O(m)}\) 的。在模 \(k\) 意義下,如果某時刻的 \(c\) 和之前一時刻的 \(c'\)

相等,則這一段矩形面積一定能被 \(k\) 整除。我們不妨開一組桶,記錄每個相同的 \(c\) 值出現了多少次,最後掃一遍 \(1\to k\),統計答案。

還是以上圖為例,紅色的線是指標,從左往右掃。在 \(i\) 的位置,\(c=S_{綠色矩形}\)\(c\equiv m(\mod k)\);走到 \(j\) 的位置,\(c=S_{藍色矩形}\),恰巧又有 \(c\equiv m(\mod k)\)。因此 \(k\vert S_{紅色矩形}\),答案++……掃完之後,\(\mod k\) 結果為 \(m\) 的結果記為 \(cnt_m\),其對答案的總貢獻為 \(cnt_m\times (cnt_m-1)/2\)

特別地,我們應當初始化 \(cnt_0\gets 1\),否則如果一個矩形本身面積為 \(k\) 的倍數,那它自己的 \(1\) 發貢獻將被漏算,導致 WA 穿地心。

下面是 AC 程式碼:

int main(){
	int n,m,k; read(n),read(m),read(k);
	rep(i,1,n) rep(j,1,m){
		int x; read(x);
		s[i][j]=(s[i-1][j]+x)%k;//對豎列求字首和
	}
	long long ans=0;
	rep(i,1,n) rep(j,i,n){ //列舉區間(縱向)
		int c=0,t=1; cnt[0]=1,v[1]=0;
		rep(p,1,m){//指標掃描(橫向)
			if((c+=s[j][p]-s[i-1][p])<0) c+=k;
			if(c>=k) c-=k;//相當於取模
			!cnt[c]?cnt[v[++t]=c]=1:++cnt[c];//扔進桶裡
		}
		rep(p,1,t){
			int& x=cnt[v[p]];//沒別的意思,壓行罷了
			ans+=x*(x-1)/2,x=0;
		}
	}
	write(ans),EN;
}

THE END