Loading

[AGC036D] Negative Cycle

题意

一张有向图,初始有边 \(\forall i\in[1,n-1],i\to i+1\),边权为 \(0\)。后来加入 \(n\times (n-1)\) 条边,是对于每一对 \(i,j(i\not ={j})\),连边 \(i\to j\),若 \(i<j\),边权为 \(-1\),否则边权为 \(1\)

删去后来加入的每条边都需要一定的代价,删去 \(i\to j\) 需要花费 \(A_{i,j}\),求使得图中不存在负环。

Solution

若直接考虑去掉负环,发现负环的可能太多了,于是考虑转化题意。考虑什么地方会用到负环。差分约束! 于是我们把题目中每一条连边逆向翻译成差分约束的描述:

  1. \(x_i\ge x_{i+1}\)
  2. \(i<j\),则有:\(x_i-1\ge x_j\)
  3. \(i>j\),则有:\(x_i+1\ge x_j\)

相当于说我们要在后面两条中删去一些条件使得不等式组有解。首先根据第一条,这是一个递减的序列。这样的话,一旦我们保留了边 \(i\to j(i<j)\),那么边 \(i\to k(k>j)\)肯定是被保留的,因为 \(x_i-1\ge x_j\ge x_k\),必然能够满足。同理,如果我们保留了边 \(i\to j(i>j)\),那么边 \(i\to k(j<k<i)\) 必然被保留。我们只需要考虑从哪里开始保留。

接下来考虑如何解决这个问题。实际上,我们就是需要构造一组解,使得不等式满足这个解的花费最小。由于我们需要构造一个非严格下降序列,容易想到用 \(dp\) 来维护。根据上面的分析,我们知道,对于一个位置 \(i\),它向后的连边,当且仅当 \(x_i=x_j\) 需要删掉;向前的连边,当且仅当 \(x_j\ge x_i+2\) 需要删掉。于是令 \(dp_{i,j,k}\) 表示前 \(i\) 个点,当前连续相等段的长度是 \(j\),上一个连续相等段的长度是 \(k\),所需要删去的最小花费。

这是可能有问题的,因为最终解不一定相邻两端中的数刚好是相差 \(1\) 的。但你仔细一想,这样肯定不优,因为如果我把它调整到相差 \(1\),凭空会有向前的边可以不删。

转移需要分讨,若当前 \(j>1\),则:

\[dp_{i,j,k}=dp_{i-1,j-1,k}+\sum_{p\le i-j-k}A_{i,p}+\sum_{p\in[i-j+1,i-1]}A_{p,i} \]

我们对 \(A\) 做前缀和优化上面的东西就可以做到 \(O(1)\) 转移。

若当前 \(j=1\),则:

\[dp_{i,1,k}=\min_{l}\{dp_{i-1,k,l}+\sum_{p\le i-k-1}A_{i,p}\} \]

虽然说可以对 \(dp\) 前缀优化做到 \(O(1)\) 转移,但是在 \(dp\) 的每 \(O(n)\) 个状态中才会有一个 \(j=1\),所以暴力转移总复杂度还是 \(O(n^3)\),没有优化的必要。

\(dp\) 可以滚动一维优化空间。

然后直接在 \(dp_{n,\sim,\sim}\) 中找到最小值就是答案。

细节: 注意当且仅当 \(j=i\)\(k=0\) 是合法的,不然总是存在上一段。而 \(j\) 是不能等于 \(0\) 的。建议对 \(i=1\) 特判。

Code

// Problem: 
//     [AGC036D] Negative Cycle
//   
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/AT_agc036_d
// Memory Limit: 1 MB
// Time Limit: 2000 ms

#include<bits/stdc++.h>
#define ll long long
#define inf (1<<30)
#define INF (1ll<<60)
#define pb emplace_back
#define pii pair<int,int>
#define mkp make_pair
#define fi first
#define se second
#define all(a) a.begin(),a.end()
#define siz(a) (int)a.size()
#define clr(a) memset(a,0,sizeof(a))
#define rep(i,j,k) for(int i=(j);i<=(k);i++)
#define per(i,j,k) for(int i=(j);i>=(k);i--)
#define pt(a) cerr<<#a<<'='<<a<<' '
#define pts(a) cerr<<#a<<'='<<a<<'\n'
#define int long long
using namespace std;
const int MAXN=510;
int A[MAXN][MAXN],dp[2][MAXN][MAXN];
int A1[MAXN][MAXN],A2[MAXN][MAXN];
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	int n;cin>>n;
	rep(i,1,n) rep(j,1,n) if(i^j) cin>>A[i][j];
	rep(i,1,n) rep(j,1,n) A1[i][j]=A1[i-1][j]+A[i][j];
	rep(i,1,n) rep(j,1,n) A2[i][j]=A2[i][j-1]+A[i][j];
	memset(dp[1],0x3f,sizeof(dp[1]));
	dp[1][1][0]=0;
	rep(i,2,n){
		memset(dp[i&1],0x3f,sizeof(dp[i&1]));
		rep(k,1,i-1) rep(l,1-(k==i-1),i-k)
			dp[i&1][1][k]=min(dp[i&1][1][k],dp[(i-1)&1][k][l]+A2[i][i-k-1]);
		rep(j,2,i) rep(k,1-(j==i),i-j)
			dp[i&1][j][k]=dp[(i-1)&1][j-1][k]+A2[i][i-j-k]+A1[i-1][i]-A1[i-j][i];
	}
	int ans=INF;
	rep(j,1,n) rep(k,1-(j==n),n-j)
		ans=min(ans,dp[n&1][j][k]);
	cout<<ans<<'\n';
	return 0;
}
posted @ 2022-11-15 20:29  ZCETHAN  阅读(27)  评论(0编辑  收藏  举报