题目
点这里看题目。
分析
好妙的题!
初看起来无从下手(我最初一直想着要分行依次叠加贡献),这样的话,我们不妨来看一下,第一步应该怎么计算贡献。
面对区间 ([1,m]) ,一种方法是首先选出一列 (k),然后最大化这一列上的和——显然就是 (n) 。接着,所有经过了 (k) 的区间都不能再用了。那么问题自然地被划分成了 ([1,k-1]) 和 ([k+1,m]) 两个独立的子问题。
这提醒了我们,可以使用区间 DP 。可以考虑定义状态为:
(f(i,j)) :左右端点都在 ([i,j]) 内的区间的最大贡献和。
转移比较显然。记 (S(i,j,k)) 为所有的左右端点都在 ([i,j]) 中,且经过了 (k) 这个位置的区间数量。那么就有转移:
[f(i,j)=max{f(i,k-1)+f(k+1,j)+(S(i,j,k))^2|ile kle j}
]
这里的 (S) 理论上来说,可以直接 (O(n^3)) 地预处理出来(高位前缀和)。实际上直接 (O(n^4)) 也无伤大雅。
总时间复杂度就是 (O(n^3)sim O(n^4)) 。
小结:
本题的突破口就在于最初对于贡献计算的思考,所以如果最初没有思路,就可以先对问题进行一些基础的分析。
代码
#include <cstdio>
const int INF = 0x3f3f3f3f;
const int MAXN = 105;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while( s < '0' || '9' < s ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ); s = getchar(); }
x *= f;
}
template<typename _T>
void write( _T x )
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}
template<typename _T>
_T MAX( const _T a, const _T b )
{
return a > b ? a : b;
}
int f[MAXN][MAXN];
int su[MAXN][MAXN][MAXN];
int N, M;
int main()
{
read( N ), read( M );
for( int i = 1 ; i <= N ; i ++ )
{
int K, l, r;
read( K );
while( K -- )
{
read( l ), read( r );
for( int a = 1 ; a <= l ; a ++ )
for( int b = r ; b <= M ; b ++ )
su[a][b][l] ++, su[a][b][r + 1] --;
}
}
for( int i = 1 ; i <= M ; i ++ )
for( int j = i + 1 ; j <= M ; j ++ )
for( int k = i ; k <= j ; k ++ )
su[i][j][k] += su[i][j][k - 1];
for( int i = M ; i ; i -- )
{
f[i][i + 1] = -INF;
for( int j = i ; j <= M ; j ++ )
for( int k = i ; k <= j ; k ++ )
f[i][j] = MAX( f[i][j], f[i][k - 1] + f[k + 1][j] + su[i][j][k] * su[i][j][k] );
}
write( f[1][M] ), putchar( '
' );
return 0;
}