AT5147-[AGC036D]Negative Cycle【dp,模型转换】
正题
题目链接:https://www.luogu.com.cn/problem/AT5147
题目大意
有\(n\)个点的一张图,其中\(i\rightarrow i+1(i< n)\)有一条边权值为\(0\)。
对于其他\(i,j(i\neq j)\)存在一条边\(i\rightarrow j\),若\(i<j\)那么权值为\(-1\),否则为\(1\)。
删除\(i\rightarrow j(i\neq j)\)的代价为\(a_{i,j}\),要求代价最小的情况下使得图中不存在负环。
\(1\leq n\leq 500\)
解题思路
这个容易负环让人一头雾水不知道怎么维护,我们知道差分约束有解的条件就是没有负环,所以我们可以考虑转成差分约束模型。
那么对于不能删除的边\(i\rightarrow i+1\)就有限制\(x_i\geq x_{i+1}\),也就是整个序列单调不升。
然后形如\(i\rightarrow j(i<j)\)可以视为\(x_i-1\geq x_j\)。
形如\(i\leftarrow j(i<j)\)可以视为\(x_i\leq x_j+1\)。
也就是\(x_i-x_j\leq 1\)和\(x_i-x_j\geq 1\)的限制,我们考虑维护差分数组(反过来的)\(y_i=x_{i-1}-x_{i}\),那么条件就是区间和不能大于/小于\(1\),显然的那么\(y_i\)就只有可能是\(0/1\)。
然后仔细观察这个限制会发现其实只有相邻的\(1\)会有用,我们考虑\(dp\)来处理,设\(f_{i,j,k}\)表示做到第\(i\)个,上一个\(1\)在\(j\)处,再上一个\(1\)在\(k\)处。
前缀和一下\(a\)数组优化转移即可。
时间复杂度:\(O(n^3)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=510;
ll n,a[N][N],s[N][N],t[N][N],f[N][N],ans;
signed main()
{
scanf("%lld",&n);
for(ll i=1;i<=n;i++)
for(ll j=1;j<=n;j++){
if(i==j)continue;
scanf("%lld",&a[i][j]);
}
for(ll i=1;i<=n;i++)
for(ll j=1;j<=i;j++)
s[i][j]=s[i][j-1]+a[j][i],
t[i][j]=t[i][j-1]+a[i][j];
memset(f,0x3f,sizeof(f));
ans=f[0][0];f[1][1]=0;
for(ll i=2;i<=n;i++){
for(ll j=1;j<i;j++)
for(ll k=1;k<=j-(j!=1);k++)
f[i][j]=min(f[i][j],f[j][k]);
for(ll j=1;j<=i;j++)
for(ll k=1;k<=j-(j!=1);k++)
f[j][k]+=s[i][i]-s[i][j-1],f[j][k]+=t[i][k-1];
}
for(ll i=1;i<=n;i++)
for(ll j=1;j<=n;j++)
ans=min(ans,f[i][j]);
printf("%lld\n",ans);
return 0;
}