【luogu AT5147】Negative Cycle(差分约束)(DP)

Negative Cycle

题目链接:luogu AT5147

题目大意

给你一个有向图,有 i 到 i+1 的边,边权为 0。
然后对于不相等的 i,j 之间,有一条 i 到 j 的边,如果从小到大边权为 -1,否则为 1,然后可以被你删去,有一个费用 ai,j。
然后要你用最小的费用使得图中不存在负环。

思路

首先由一个转化就是看到负环那存在的条件就是差分约束无解。
(因为你发现正常来搞好像不太能看负环之类的)

然后看到有不能被删的边,考虑从它下手,发现条件是 x i ⩾ x i + 1 x_i\geqslant x_{i+1} xixi+1

然后接着看可以删掉的:
i → j ( i < j ) : x i − 1 ⩾ x j i\rightarrow j(i<j):x_i-1\geqslant x_j ij(i<j):xi1xj
i → j ( i > j ) : x i + 1 ⩾ x j i\rightarrow j(i>j):x_i+1\geqslant x_j ij(i>j):xi+1xj

然后因为好观察所以我们让 i < j i<j i<j,那应该是:
i → j ( i < j ) : x i − 1 ⩾ x j i\rightarrow j(i<j):x_i-1\geqslant x_j ij(i<j):xi1xj
j → i ( i < j ) : x j + 1 ⩾ x i j\rightarrow i(i<j):x_j+1\geqslant x_i ji(i<j):xj+1xi
整理一下:
x i − x j ⩾ 1 x_i-x_j\geqslant 1 xixj1
x i − x j ⩽ 1 x_i-x_j\leqslant 1 xixj1

考虑怎么跟上面的那个联系起来,发现上面那个可以变成:
x i − x i + 1 ⩾ 0 x_{i}-x_{i+1}\geqslant 0 xixi+10
然后就是类似于差分的形式,然后那连续的差分加起来就是头减尾。
那不就跟上面的那个一样了吗!
y i = x i − x i + 1 ⩾ 0 y_i=x_i-x_{i+1}\geqslant 0 yi=xixi+10,那两个条件就是 ∑ p = i j y i ⩾ 1 \sum\limits_{p=i}^jy_i\geqslant 1 p=ijyi1 ⩽ 1 \leqslant 1 1

那也就是条件是不能 < 1 , > 1 <1,>1 <1,>1 这两种,那显然的我们的取值由于 y ⩾ 0 y\geqslant 0 y0 就只能是 0 / 1 0/1 0/1(整数)。
那我们可以看到我们所要注意的就是相邻的 1 1 1 之间的位置,具体而言我们放一个 1 1 1 的时候要注意前面两个 1 1 1 的位置。
因为两个 1 1 1 之间是 < 1 <1 <1 要用贡献删去。
到前面的那个 1 1 1 的位置的时候积累了两个 1 1 1,就也要用贡献。

f i , j f_{i,j} fi,j 为倒数那个在 i i i,倒数两个在 j j j,DP 转移即可。
至于转移系数那些,考虑每次到一个位置,那之前的 f i , j f_{i,j} fi,j 加贡献因为无论放不放这个位置到前面那些的都会有,然后在看是不是放把下标转移一下。
那你加的是一段的贡献,这个简单直接前缀和预处理一下就可以了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long

using namespace std;

const int N = 505;
int n, a[N][N];
ll f[N][N], ans, sb[N][N], bs[N][N];
//sb:small big
//bs:big small

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++) {
			if (i == j) continue;
			scanf("%d", &a[i][j]);
		}
	
	for (int i = 1; i <= n; i++)
		for (int j = i; j <= n; j++)
			sb[i][j] = sb[i - 1][j] + a[i][j];
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= i; j++)
			bs[i][j] = bs[i][j - 1] + a[i][j];
	
	memset(f, 0x7f, sizeof(f));
	f[1][1] = 0;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j < i; j++)
			for (int k = 1; k <= j - !!(j - 1); k++)
				f[i][j] = min(f[i][j], f[j][k]);
		for (int j = 1; j <= i; j++)
			for (int k = 1; k <= j - !!(j - 1); k++)
				f[j][k] += (sb[i][i] - sb[j - 1][i]) + (bs[i][k - 1]);
	}
	ans = f[0][0];
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			ans = min(ans, f[i][j]);
	printf("%lld", ans);
		
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值