[AGC036D] Negative Cycle 题解
\(\text{[AGC036D] Negative Cycle 题解}\)
这个题还是有点意思的。
看上去我们没有足够的大脑想到这东西是个差分约束模型,但我们可以注意到不能删去的那些权值为 \(0\) 的边比较 special。关注这些边,发现它们的作用实际上是将不连续的负权边串起来,换句话说对于 \([l,r]\),考虑用正权边来限制负权边,若有正权边 \(r\to l\),那么区间内不能有两条满足 \(r_i\le l_j\) 的线段,否则显然是不合法的。这样一来我们得到了一个正权边对于负权边的限制条件,直接用正权边这个东西来 dp 相当于钦定了正权边的选择,由于这个条件并不充要,因此可能统计不到更优的正权边条件,于是让我们进一步发现性质来统计答案。对于一条正权边,我们相当于将区间内分成了左右两部分分别作为出边和入边,这样做的意义实际上是要求钦定正权边只通过一个区间内或是在两个相邻区间之间连边来保证环上没有更多的负权边,于是实际上这个题就做完了。我们设计一个 dp 表示当前区间的末尾是 \(i\),上一个区间末尾是 \(j\) 时的答案,转移的时候枚举上上个区间的末尾 \(k\),钦定删去 \([1,k-1]\to[j,i]\) 之间的正权边和区间 \([j,i]\) 之内的负权边即可。至于上面那个东西的统计方式就是随便前缀和一下就好了。
代码:
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 505;
int n;
int sa[N][N], sb[N][N];
int s(int x, int y) {
if (x < 0 || y < 0) return 0;
return sa[x][y];
}
int gt(int l1, int r1, int l2, int r2) {
if (l1 > r1 || l2 > r2) return 0;
return s(r1, r2) - s(l1 - 1, r2) - s(r1, l2 - 1) + s(l1 - 1, l2 - 1);
}
int dp[N][N];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) cin >> sa[j][i];
for (int j = i + 1; j <= n; j++) cin >> sb[i][j];
}
for (int i = 1; i <= n; i++)
for (int j = i + 1; j <= n; j++)
sa[i][j] = sa[i][j - 1] + sa[i - 1][j] - sa[i - 1][j - 1] + sa[i][j];
for (int l = 2; l <= n; l++)
for (int i = 1; i + l - 1 <= n; i++) {
int j = i + l - 1;
sb[i][j] = sb[i + 1][j] + sb[i][j - 1] - sb[i + 1][j - 1] + sb[i][j];
}
memset(dp, 0x3f, sizeof dp);
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++)
dp[i][j] = min(dp[i][j], dp[j - 1][k] + sb[j][i] + gt(1, k - 1, j, i));
}
int ans = 9e18;
for (int i = 1; i <= n; i++) ans = min(ans, dp[n][i]);
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号