[AGC036D] Negative Cycle

题意#

一张有向图,初始有边 i[1,n1],ii+1,边权为 0。后来加入 n×(n1) 条边,是对于每一对 i,j(ij),连边 ij,若 i<j,边权为 1,否则边权为 1

删去后来加入的每条边都需要一定的代价,删去 ij 需要花费 Ai,j,求使得图中不存在负环。

Solution#

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

  1. xixi+1
  2. i<j,则有:xi1xj
  3. i>j,则有:xi+1xj

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

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

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

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

dpi,j,k=dpi1,j1,k+pijkAi,p+p[ij+1,i1]Ap,i

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

若当前 j=1,则:

dpi,1,k=minl{dpi1,k,l+pik1Ai,p}

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

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

然后直接在 dpn,, 中找到最小值就是答案。

细节: 注意当且仅当 j=ik=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 @   ZCETHAN  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
历史上的今天:
2021-11-15 XJOI 2021.11.14 嗯哦癌批冲剌赛
2021-11-15 XJOI 2021.11.13 嗯哦癌批冲剌赛
2021-11-15 学习笔记——线段树合并
点击右上角即可分享
微信分享提示
主题色彩