当前位置: 代码迷 >> 综合 >> AtCoder AGC036D Negative Cycle - solution
  详细解决方案

AtCoder AGC036D Negative Cycle - solution

热度:6   发布时间:2024-01-25 23:14:04.0

今天做了场学长“咸鱼lzh”出的ACM模拟赛,而这道毒瘤题作为第一道,并且是最难的。

1.【题目大意】

一张n个点的图,i到 i+1有连边,边权为0。
Snuke会给所有(i,j)连边,如果 i<j,边权为?1,否则为1。
但是Ringo不想要图里有负环,所以他会删去Snuke加的一些边,使得图中没有负环,删除一条边有个代价,问最小的删边代价。

2.【考虑算法】

直接看题,似乎非常懵逼,想不到什么算法,连暴力都有些麻烦。

所以,我们可以从数据入手,仔细看题目,会发现所有边权都是0或者1或者-1,并且不存在负环!所以,我们很容易想到差分约束系统。

3.【细致解法】

令d[i]为从1号点出发到第i号点的最短路。不存在负环说明就有最短路,等价于我们能够给di赋一个数值,使得对于任意一条边e[i,j]我们都有d[j]≤d[i]+e[i,j],三角形不等式。

不存在负环的时候,显然会有d[i]≥d[i+1]。我们不妨考虑d数组差分后的值。

令q[i]=d[i+1]-d[i],考虑一条边i—>j(i<j),它的限制相当于是d[j]≤d[i]-1,即q[i]+q[i+1]+…+q[j-1]≥1。而对于一条边j—>i(i<j),它的限制相当于是d[i]≤d[j]+1,即q[i]+q[i+1]+…+q[j-1]≤1。

然后我们可以证明出,q[i]∈{0,1},这里比较容易,如果q[i]<0原链的差分约束条件就不满足,如果 q[i]>0 则点i+1存在额外的?1入边 (v,i+1),v<i,此时v到i最坏情况可以走一段0链更新,所以q[i]最多只能为1。

也可以更容易的理解为由于边权都是1或者?1并且存在不能删的0边, 在没有负环的情况下d[i]≤d[i+1]+1,所以d[i]-d[i+1]=0/1,从而推出q[i]=d[i]-d[i+1]是0或者1,

然后我们就可以考虑 q[i]是取 0 还是取 1 ,然后删掉不合法的边,这个过程是可以 DP 解决的,对于不满足∑q[i] ≤1的情况,在其跨过第二个 1 的时候统计掉,对于∑q[i] ≥1的情况,对于每一段连续的 0 统计即可,设f[i,j]表示安排好前i位的q值,且强行令qi=1,上一个为1的位置是j,那么考虑枚举k, f[i,j]转移到f[k,i]。最后,删去不满足约束条件的状态,对于(a,b):

1.如果a>b,要删掉所有满足j<b<i<x<a的边。

2.如果a<b,要删掉所有满足j<i<a<b≤x的边。

这些判断在前缀和中搞定。

4.【代码解析】

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline int read(){int x=0; bool f=1; char c=getchar();for(;!isdigit(c);c=getchar()) if(c=='-') f=0;for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+(c^'0');if(f) return x;return -x;
}
const int N = 500;
LL a[N+3][N+3],s[2][N+3][N+3],f[N+3][N+3];
int n;
void update(LL &x,LL y) {x = x<y?x:y;}
LL sum(int t,int lx,int rx,int ly,int ry){return s[t][rx][ry]-s[t][lx-1][ry]-s[t][rx][ly-1]+s[t][lx-1][ly-1];
}//求区间前缀和,用到了容斥的思想,s[t][lx-1][ry]和s[t][rx][ly-1]减去了两次s[t][lx-1][ly-1],而s[t][rx][ry]中抵消了一次,还需增加一次s[t][lx-1][ly-1]。
int main(){scanf("%d",&n);for(int i=1;i<=n;i++){for(int j=1; j<=n; j++){if(j==i) continue;//自己和自己没有连边scanf("%lld",&a[i][j]);}}for(int i=1; i<=n; i++){for(int j=1; j<=n; j++){if(i<j) s[0][i][j]=a[i][j];s[0][i][j]+=s[0][i][j-1];}for(int j=1;j<=n;j++)s[0][i][j]+=s[0][i-1][j];}for(int i=1; i<=n; i++){for(int j=1; j<=n; j++){if(i>j)s[1][i][j]=a[i][j];s[1][i][j]+=s[1][i][j-1];}for(int j=1;j<=n;j++)s[1][i][j]+=s[1][i-1][j];}//上面两段是为了dp优化的,有i<j和i>j两种情况,两个二维前缀和维护。memset(f,42,sizeof(f)); //下面是三个数相加,防止爆炸,42=127/3。f[0][0]=0ll;for(int i=0;i<=n; i++)for(int j=0;j<max(i,1);j++)for(int k=i+1;k<=n;k++){LL tmp=f[i][j]+sum(1,k+1,n,j+1,i)+sum(0,i+1,k,i+1,k);update(f[k][i],tmp);/* 从f[i][j]转移到f[k][j],转移所需的代价是s[1]中(k+1,j+1)至(n,i)的                 代价加上s[0]中(i+1,i+1)至(k,k)的代价。*/}LL ans=f[n][1];for(int i=1;i<=n;i++)update(ans,f[n][i]);//哪一位q[i]是1都可以,取最小。printf("%lld\n",ans);return 0;
}

5.【效率分析】

状态数O(n*n),转移O(n),总复杂度O(n^3),可得100分。

  相关解决方案