AGC036D 题解
跟现有题解的推法不太一样。这确实是道非常有 AtCoder 风格的题。
首先看到这种一眼没有多项式做法的题,考虑找性质简化问题。
我们注意到有
Key observation:如果我们确定了一条从
证明:考虑证明加入这条边后不影响合法性,如果保留边
既然不影响合法性,所有边权值还都
所以,最终答案的负边一定连成如下形式:
其中线段表示序列上的一个个区间,区间内部不连边,圆点表示一些孤立点。所有非孤立点向在它后面与其不同区间的非孤立点都有连边。
进一步观察可以发现,孤立点是不存在的,因为将孤立点并入其旁边的区间肯定不劣,这个操作不会影响正边怎么连。
于是在最优解中,原序列可以被划分为
问题转化到这里就很好做了。设
#include <bits/stdc++.h>
using namespace std;
const int N = 5e2 + 5;
typedef long long ll;
template<typename T> inline bool ckmin(T &x,const T &y) { return (x > y) && (x = y,true);}
int n;
int B[N][N],A[N][N]; // A+,B-
ll dp[N][N];
ll sa[N][N],sb[N][N];
inline ll Query(ll s[N][N],int l1,int r1,int l2,int r2) {
if(l1 == 0 && l2 == 0) return s[r1][r2];
if(l1 == 0) return s[r1][r2] - s[r1][l2 - 1];
if(l2 == 0) return s[r1][r2] - s[l1 - 1][r2];
return s[r1][r2] - s[l1 - 1][r2] - s[r1][l2 - 1] + s[l1 - 1][l2 - 1];
}
int main() {
cin >> n;
for(int i = 1;i <= n;i++) {
for(int j = 1;j < i;j++) cin >> A[j][i];
for(int j = i + 1;j <= n;j++) cin >> B[i][j];
}
for(int i = 1;i <= n;i++)
for(int j = 1;j <= n;j++)
sa[i][j] = sa[i][j - 1] + sa[i - 1][j] - sa[i - 1][j - 1] + A[i][j],
sb[i][j] = sb[i][j - 1] + sb[i - 1][j] - sb[i - 1][j - 1] + B[i][j];
memset(dp,0x3f,sizeof dp);
ll ans = 1e18;
dp[0][0] = 0;
for(int i = 1;i <= n;i++)
for(int j = 1;j <= i;j++)
for(int k = 0;k < j;k++)
ckmin(dp[j][i],dp[k][j - 1] + Query(sb,j,i,j,i) - Query(sa,j,i,j,i) - Query(sa,k,j - 1,j,i));
for(int i = 1;i <= n;i++) ckmin(ans,dp[i][n]);
ans += sa[n][n];
cout << ans << endl;
return 0;
}