【題解】CF1372E Omkar and Last Floor
阿新 • • 發佈:2021-07-13
\(\text{Solution:}\)
首先一個顯然的結論是讓每一列的 \(1\) 的個數越多越好。
證明: \((a+b)^2>a^2+b^2.\)
於是我們應該考慮的是怎麼讓一列的數的和最大。
發現我們可以合併兩個區間的答案,而資料範圍這麼小,很容易讓人想到 \(O(n^3)\) 的演算法。
試著考慮區間 \(dp:\) 設 \(f[l][r]\) 表示區間 \([l,r]\) 的最大平方和。
然後會發現:這個端點可能跨越了一些段,導致很難轉移。
於是我們更改一下:設 \(f[l][r]\) 為所有端點都在 \([l,r]\)
其中 \(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; }