• @atcoder



    @description@

    给定一个 N 个点的有向带权图,从 0 编号到 N - 1。一开始这张图有 N - 1 条边,第 i 条边连接点 i 与点 i+1,边权为 0。

    接着往这张图加边:对于每一对 (i, j)(i ≠ j),连 i -> j,当 i < j 时边权为 -1;否则边权为 1。

    我们想要删掉一些边 (i, j)(i ≠ j),使得这张图不存在负环。删掉边 (i, j) 的费用为 A(i, j)。
    请找到最小的删边费用,使得图中不存在负环。只能删之后加的边。

    Constraints
    3≤N≤500
    1≤A(i, j)≤10^9
    所有数都为整数。

    Input
    输入的格式如下:
    N
    A0,1 A0,2 A0,3 ⋯ A0,N−1
    A1,0 A1,2 A1,3 ⋯ A1,N−1
    A2,0 A2,1 A2,3 ⋯ A2,N−1

    AN−1,0 AN−1,1 AN−1,2 ⋯ AN−1,N−2

    Output
    输出最小的删边费用。

    Sample Input 1
    3
    2 1
    1 4
    3 3
    Sample Output 1
    2

    Sample Input 2
    4
    1 1 1
    1 1 1
    1 1 1
    1 1 1
    Sample Output 2
    2

    @solution@

    “感觉就是凑巧想到了这个做法,觉得有点道理于是逆向出了一道题。”——By lsk。

    如果没有负环,意味着最短路存在。我们就从最短路出发来思考。
    最短路有一个很经典的结论:三角形不等式。即对于任意一条边权为 w 的边 (u, v) 始终有 dis[u] + w >= dis[v]。
    因此我们再从三角形不等式出发,进一步剖析这道题。

    令最终的图中 0 号点到第 i 个点的距离为 dis[i]。因为边权为 0 的边存在,所以有 dis[i] >= dis[i+1]。其中还有 dis[0] = 0。
    对于 i->j (i < j) 的边,有 dis[i] - 1 >= dis[j];对于 i->j (i > j) 的边,有 dis[i] + 1 >= dis[j]。
    由于 dis 应该是满足三角形不等式的最大值,在没有负环的情况下有 dis[i] - 1 <= dis[i+1]。

    注意到上面的不等式都是在 dis[i] 与 dis[i+1] 之间建立的,我们可以考虑差分:令 p[i] = dis[i] - dis[i + 1](注意不是 dis[i+1] - dis[i]),则有 0 <= p[i] <= 1。
    对于 i->j (i < j) 的边,应该满足 (sum_{k=i}^{j-1}p[k] ge 1);对于 i->j (i > j) 的边,应该满足 (sum_{k=j}^{i-1}p[k] le 1)。这个可以根据上面的三角形不等式推导得到。
    即:一个需要区间内至少包含 1 个 1,一个需要区间内最多包含 1 个 1。

    然后我们就可以对这个 p 进行 dp 了。设 dp[i][j][k] 表示处理完前 i 位,往前数第 1 个 1 在 j 位置,第 2 个 1 在 k 位置。
    转移时枚举第 i + 1 位填 0 还是填 1 即可。第一维显然可以滚动掉。
    时间复杂度 O(n^3)。

    @accepted code@

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    typedef long long ll;
    const int MAXN = 500;
    const ll INF = ll(1E18);
    int A[MAXN + 5][MAXN + 5], B[MAXN + 5][MAXN + 5], n;
    // A[i][j] -> (p[i] + p[i+1] + ... p[j] >= 1) (i <= j)
    // B[i][j] -> (p[i] + p[i+1] + ... p[j] <= 1) (i <= j)
    ll SA[MAXN + 5][MAXN + 5], SB[MAXN + 5][MAXN + 5];
    // SA[i][j] -> A[1][j] + A[2][j] + ... + A[i][j]
    // SB[i][j] -> B[1][j] + B[2][j] + ... + B[i][j]
    ll dp[2][MAXN + 5][MAXN + 5], f[2][MAXN + 5], g[2];
    int main() {
    	scanf("%d", &n);
    	for(int i=1;i<=n;i++) {
    		for(int j=1;j<=i-1;j++) scanf("%d", &B[j][i-1]); // i -> j (i > j)
    		for(int j=i+1;j<=n;j++) scanf("%d", &A[i][j-1]);
    	}
    	for(int j=1;j<=n;j++)
    		for(int i=1;i<=n;i++)
    			SA[i][j] = A[i][j] + SA[i-1][j], SB[i][j] = B[i][j] + SB[i-1][j];
    	n--;
    	for(int j=1;j<=n;j++) {
    		for(int k=j+1;k<=n;k++)
    			dp[0][j][k] = INF;
    		f[0][j] = INF;
    	}
    	g[0] = 0;
    	for(int i=1;i<=n;i++) {
    		for(int j=1;j<i;j++) {
    			for(int k=j+1;k<i;k++)
    				dp[1][j][k] = dp[0][j][k], dp[0][j][k] = INF;
    			f[1][j] = f[0][j], f[0][j] = INF;
    		}
    		g[1] = g[0], g[0] = INF;
    		for(int j=1;j<i;j++) {
    			for(int k=j+1;k<i;k++) {
    				dp[0][j][k] = min(dp[0][j][k], dp[1][j][k] + SA[i][i] - SA[k][i] + SB[j][i]);
    				dp[0][k][i] = min(dp[0][k][i], dp[1][j][k] + SB[k][i]);
    			}
    			f[0][j] = min(f[0][j], f[1][j] + SA[i][i] - SA[j][i]);
    			dp[0][j][i] = min(dp[0][j][i], f[1][j] + SB[j][i]);
    		}
    		g[0] = min(g[0], g[1] + SA[i][i]);
    		f[0][i] = min(f[0][i], g[1]);
    	}
    	ll mn = INF;
    	for(int j=1;j<=n;j++) {
    		for(int k=j+1;k<=n;k++)
    			mn = min(mn, dp[0][j][k]);
    		mn = min(mn, f[0][j]);
    	}
    	mn = min(mn, g[0]);
    	printf("%lld
    ", mn);
    }
    

    @details@

    为了方便转移,多设了几个只含 1 个 1 / 不含任何 1 的状态。

    我下一次要是再不开 long long 我就。。。

  • 相关阅读:
    【转载】[教程]OpenSEES超简单易懂的入门第一课
    【转载】面向对象的非线性有限元方法
    与李文雄老师讨论有限元
    【转载】 Moving Beyond OpenGL 1.1 for Windows
    【转载】国外免费期刊全文数据库
    与李文雄老师讨论学术研究
    【转载】VS 2010和.NET 4.0之WPF 4改进全解析
    【转载】MFC中SDI、MDI框架各部分指针获取(网上找的,好东西大家一起分享,多谢原创作者!)
    【转载】一位院士——搞科研的几个条件
    Visual Studio 2010 step by step学习摘要
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11713144.html
Copyright © 2020-2023  润新知