1. 程式人生 > 其它 >【題解】CF1372E Omkar and Last Floor

【題解】CF1372E Omkar and Last Floor

CF1372E Omkar and Last Floor

\(\text{Solution:}\)

首先一個顯然的結論是讓每一列的 \(1\) 的個數越多越好。

證明: \((a+b)^2>a^2+b^2.\)

於是我們應該考慮的是怎麼讓一列的數的和最大。

發現我們可以合併兩個區間的答案,而資料範圍這麼小,很容易讓人想到 \(O(n^3)\) 的演算法。

試著考慮區間 \(dp:\)\(f[l][r]\) 表示區間 \([l,r]\) 的最大平方和。

然後會發現:這個端點可能跨越了一些段,導致很難轉移。

於是我們更改一下:設 \(f[l][r]\) 為所有端點都在 \([l,r]\)

內的線段組合的最大平方和。那麼轉移就是:

\[f[l][r]=\max\{ f[l][k-1]+f[k+1][r]+sum^2[l][r][k]\} \]

其中 \(sum[l][r][k]\) 代表區間 \([l,r]\) 中跨越 \(k\) 的線段個數。

考慮怎麼處理這個東西:發現對於一個線段 \(l,r,k\in[l,r],[1,l-1]\cup [r+1,m]\) 內的端點才會有影響。

考慮一個以 \(l\) 為橫座標 \(r\) 為縱座標的座標系,那影響的範圍就是一個矩陣。二維字首和即可。

有一種神奇的寫法:先字首做出一條線的和,再掃成矩陣的面積。

#include<bits/stdc++.h>
using namespace std;
int n,m,f[101][101];
int sum[101][101][101];
struct line{int l,r;}p[101][101];
int cnt[101];
inline int Max(int x,int y){return x>y?x:y;}
int main(){
	freopen("111.txt","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		int x;
		scanf("%d",&x);
		for(int j=1;j<=x;++j){
			++cnt[i];
			scanf("%d%d",&p[i][cnt[i]].l,&p[i][cnt[i]].r);
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=cnt[i];++j){
			int l=p[i][j].l;
			int r=p[i][j].r;
			for(int k=l;k<=r;++k)sum[l][r][k]++;
		}
	}
	for(int k=1;k<=m;++k){
		for(int i=1;i<=m;++i){
			for(int j=1;j<=m;++j){
				sum[i][j][k]+=sum[i][j-1][k];
			}
		}
		for(int i=m;i;--i){
			for(int j=1;j<=m;++j){
				sum[i][j][k]+=sum[i+1][j][k];
			}
		}
	}
//	for(int k=1;k<=m;++k){
//		printf("%d:\n",k);
//		for(int i=1;i<=m;++i){
//			for(int j=1;j<=m;++j)printf("%d ",sum[i][j][k]);
//			puts("");
//		}
//	}
	for(int i=1;i<=m;++i)f[i][i]=sum[i][i][i]*sum[i][i][i];
	for(int len=2;len<=m;++len){
		for(int l=1;l<=m-len+1;++l){
			int r=l+len-1;
			for(int k=l;k<=r;++k){
				f[l][r]=Max(f[l][r],f[l][k-1]+f[k+1][r]+sum[l][r][k]*sum[l][r][k]);
			}
		}
	}
	printf("%d\n",f[1][m]);
	return 0;
}