2020牛客暑期多校02 I - Interval 最短路

20200715005924

2020牛客暑期多校02 I - Interval 最短路

题意

给定一个区间 [ 1 , n ] ( n ≥ 2 ) [1,n](n\geq 2) [1,n](n2),每次操作可将其下界或上界增加或减少 1 1 1,但长度至少为 1 1 1、下界最小为 1 1 1、上界最大为 n n n,即,若当前区间为 [ L , R ] [L,R] [L,R],则一次操作后可变为以下其中一种:

  • [ L − 1 , R ] [L-1,R] [L1,R](除非 L = 1 L=1 L=1);
  • [ L + 1 , R ] [L+1,R] [L+1,R](除非 L = R L=R L=R);
  • [ L , R − 1 ] [L,R-1] [L,R1](除非 L = R L=R L=R);
  • [ L , R + 1 ] [L,R+1] [L,R+1](除非 R = n R=n R=n)。

然而,我们希望区间的长度永远不要降到 1 1 1,于是要把某些操作给禁用掉。

可禁用的操作有 m m m 种,每种可描述为四元组 ( l , r , d i r , c ) (l,r,dir,c) (l,r,dir,c),表示花费 c c c 的代价,可以禁用:

  • d i r = L dir=L dir=L 时,可禁用 [ l , r ] [l,r] [l,r] [ l + 1 , r ] [l+1,r] [l+1,r] 之间互相变化的操作;
  • d i r = R dir=R dir=R 时,可禁用 [ l , r ] [l,r] [l,r] [ l , r − 1 ] [l,r-1] [l,r1] 之间互相变化的操作;

我们可从这 m m m 种中选取任意数量,并付出其代价之和。

求,总共至少要付出多大代价,才能实现目标(区间的长度永远不要降到 1 1 1)?若不可能实现目标则输出 − 1 -1 1

2 ≤ n ≤ 500 ; 0 ≤ m ≤ n ( n − 1 ) ; 2\leq n\leq 500; 0\leq m\leq n(n-1); 2n500;0mn(n1);

1 ≤ l < r ≤ n ; d i r ∈ { L , R } ; 1 ≤ c ≤ 1 0 6 ; 1\leq l<r\leq n;dir\in\{L,R\};1\leq c\leq 10^6; 1l<rn;dir{L,R};1c106;

例1(我对拍产生的):

4 10
2 3 R 18
3 4 L 7
2 3 L 69
1 2 L 45
1 3 L 90
1 2 R 69
1 4 L 47
1 3 R 97
2 4 R 41
3 4 R 41

解法一:最小割,TLE

如果把每种状态 [ L , R ] [L,R] [L,R] 视为无向图中的一个节点,则一对互相变化的操作就对应一条无向边(即 [ L , R ] [L,R] [L,R] [ L − 1 , R ] , [ L + 1 , R ] , [ L , R − 1 ] , [ L , R + 1 ] [L-1,R],[L+1,R],[L,R-1],[L,R+1] [L1,R],[L+1,R],[L,R1],[L,R+1] 连边),边权为此操作被禁用的代价(若不可被禁用则设为 + ∞ +\infin +)。那么,我们就是要取一部分边,使得去除这些边之后 [ 1 , n ] [1,n] [1,n] 对应点分别与 ∀ i , [ i , i ] \forall i, [i,i] i,[i,i] 对应点不连通,且其边权之和最小(不可能实现目标当且仅当边权之和为 + ∞ +\infin +)。

例1的示意图:黑点和黑边是我们建的图。

在这里插入图片描述

再增设一节点 T T T,并分别与 ∀ i , [ i , i ] \forall i, [i,i] i,[i,i] 连边(边权为 + ∞ +\infin +​),所有边以其边权为容量,那么问题就转化为以 [ 1 , n ] [1,n] [1,n] 对应点为源点、以 T T T 为汇点的最小割问题,其中,不可能实现目标当且仅当最小割为 + ∞ +\infin +

【WA】最小割等于最大流。值得注意的是,若点集 V V V 被分为 V 1 , V 2 V_1,V_2 V1,V2,其中 源 点 ∈ V 1 ; 汇 点 ∈ V 2 ; V 1 ∩ V 2 = ϕ ; V 1 ∪ V 2 = V 源点\in V_1; 汇点\in V_2; V_1\cap V_2=\phi; V_1\cup V_2=V V1;V2;V1V2=ϕ;V1V2=V,那么其割值是 V 1 V_1 V1 所有出边的容量(权值)之和,而不考虑入边。因为,割的含义可理解为这一条“分界线”对最大流的限制,而点集 V V V 有各种各样的割法、各有各的限制,其中割值最小的割法就成为全图的瓶颈之处,因此最小割等于最大流。既然不考虑入边,而我们的图是无向图,因此每条边实际建图时都应建双向边。此题使我对最小割的理解进一步深入。

以例1示意图为例,若只建向下、向右方向有初始容量的边,则最小割为97+18+69+47,略过了 [ 2 , 3 ] ↔ [ 2 , 4 ] [2,3]\leftrightarrow[2,4] [2,3][2,4] 之间的容量为 41 41 41 的边。

【WA】另外,对 + ∞ +\infin + 的处理也有讲究:

  • 一方面,一个有效操作的边的边权最大为 1 0 6 10^6 106,而 n = 500 n=500 n=500 时能达到目标的样例的可能割法可能需要割去约 50 0 2 500^2 5002 条边,因此答案最大可能达到 2.5 ∗ 1 0 11 2.5*10^{11} 2.51011,因此,为了判断是否不可能实现目标,每一条边权为 + ∞ +\infin + 的边的实际权值(记为 I N F 1 INF_1 INF1)至少应为 2.5 ∗ 1 0 11 2.5*10^{11} 2.51011,则求出的最小割大于等于 I N F 1 INF_1 INF1 当且仅当不可能实现目标;
  • 另一方面,最小割(最大流)计算过程中的值最大可达到 2 ∗ I N F 1 2*INF_1 2INF1(记为 I N F 2 INF_2 INF2),因为存在一种割法需要割去两条边 [ 1 , n ] ↔ [ 1 , n − 1 ] [1,n]\leftrightarrow[1,n-1] [1,n][1,n1] [ 1 , n ] ↔ [ 2 , n ] [1,n]\leftrightarrow[2,n] [1,n][2,n]。因此要使 I N F 2 INF_2 INF2 不爆 long long。 I N F 2 INF_2 INF2 在 dinic 算法的dfs函数的第一层调用时在参数列表中会用到,此处不可直接使用 I N F 1 INF_1 INF1 的值,因此有必要明确地将两个 + ∞ +\infin + 值分开讨论

由上述要求可确定两个 + ∞ +\infin + 的实际取值。

【TLE】但是最小割的思路会超时。下面介绍最短路的做法。

解法二:最短路

去除上图中的黑点黑边,得:

在这里插入图片描述

我们会发现,我们需要选择一部分绿边形成分界线,把左上角的 [ 1 , n ] [1,n] [1,n] 块与右下角的 ∀ i , [ i , i ] \forall i, [i,i] i,[i,i] 块分隔开。

为了使选中绿边的权值之和最小,我们选中的绿边所生成的图(分界线)不仅是一棵树(图上有环是没必要的),而且还是一条链(树上多余的分支也是没必要的)。

这条链的一端应该与整幅图的“最左端”相连、另一端与整幅图的“最上端”相连。如果我们设立源点、把最左端各水平边的左端点连向源点,设立汇点、把最上端各竖直边的上端点连向汇点,则问题又转化为了最短路问题!其中,红边的权值设为 + ∞ +\infin +、与源点或汇点相连的边的权值设为 0 0 0。最短路等于 + ∞ +\infin + 当且仅当不可能实现目标。

在这里插入图片描述

【RE】某些数组开小了。

【RE】memset(vis , false , sizeof ans),半天没找出来。

【WA】忘了超过 + ∞ +\infin + 应输出 − 1 -1 1

AC 代码

说明: [ L , R ] [L,R] [L,R] 对应块的左下角坐标为 [ L − 1 , R − 1 ] [L-1,R-1] [L1,R1]、右上角坐标为 [ L , R ] [L,R] [L,R],依此类推。

#include<cstdio>
#include<string.h>
#include<queue>
#define ll long long

char str[10];
ll cost0[505][505][2];

const int fu=0, fv=1, fnext=2;
int edge[600000][3];  // *2
ll cost[600000];  // *2
int last[300000];
int edges=0;

void add_edge(int u , int v , ll c){
    edge[edges][fu] = u;
    edge[edges][fv] = v;
    edge[edges][fnext] = last[u];
    last[u] = edges;
    cost[edges] = c;
    ++edges;
}

void add_edges(int u , int v , ll c){
    add_edge(u,v,c);
    add_edge(v,u,c);
}

// dijkstra
struct Node{
    int v;
    ll dis;
    Node(int _v=0 , ll _dis=0):v(_v),dis(_dis){}
    bool operator < (const Node &n2) const{ return dis > n2.dis; }
};
std::priority_queue<Node> que;
bool vis[300000];
ll ans[300000];

int main(){
    int n,m;
    scanf("%d%d" , &n , &m);

	for(int L=1 ; L<=n ; ++L) for(int R=L+1 ; R<=n ; ++R) cost0[L][R][0]=cost0[L][R][1]=1e12;
	//printf("cost[1][2][0]=%lld\n" , cost[1][2][0]);
	for(int i=0 ; i<m ; ++i){
        int L,R,c;
        scanf("%d%d%s%d" , &L , &R , str , &c);
        cost0[L][R][ ((str[0]=='L')?1:0) ] = c;
	}

	memset(last , -1 , sizeof last);
	edges = 0;
	for(int L=1 ; L<=n ; ++L){
        for(int R=L+1 ; R<=n ; ++R){
            add_edges( (L-1)*501+(R-1) , L*501+(R-1) , cost0[L][R][0] );
            add_edges(     L*501+R     , L*501+(R-1) , cost0[L][R][1] );
        }
	}
	int S = 501*501;   for(int L=1 ; L<n ; ++L) add_edges( L*501+n , S , 0 );
    int T = 501*501+1; for(int R=1 ; R<n ; ++R) add_edges( R       , T , 0 );

    memset(vis , false , sizeof vis);  // not sizeof ans
    memset(ans , 0x3f , sizeof ans);
    //while(! que.empty()) que.pop();
    ans[S]=0;
    que.push(Node(S,ans[S]));
    while(! que.empty()){
        int u = que.top().v; que.pop();
        if(vis[u]) continue;
        vis[u] = true;
        for(int e=last[u] ; e>=0 ; e=edge[e][fnext]){
            int v = edge[e][fv];
            if( vis[v]==false && ans[v]>ans[u]+cost[e]){
                ans[v] = ans[u]+cost[e];
                que.push(Node(v,ans[v]));
            }
        }
    }

    if(ans[T]>=1e12) ans[T]=-1;  // forgot
    printf("%lld\n" , ans[T]);


    return 0;
}

附:画图研究工具(配合 StdDraw.java

import java.util.Scanner;

public class A20200713_nc02_I {
	
	public static int ran(int L , int R) {
		return (int)(Math.random()*(R-L+1))+L;
	}

	public static void main(String[] args) {
		Scanner mi = new Scanner(System.in);
		int n = mi.nextInt();
		int m = mi.nextInt();
		
		StdDraw.setScale(-2 , n+1);
		

		StdDraw.setPenRadius(0.01);
		StdDraw.setPenColor(StdDraw.PINK);
		for(int L=1 ; L<=n ; ++L) StdDraw.line(L , n , n/2 , n+0.5);
		for(int R=1 ; R<=n ; ++R) StdDraw.line(-1.5 , n/2 , 0 , R-1);
		StdDraw.setPenColor(StdDraw.BLACK);
		for(int L=1 ; L<=n ; ++L) StdDraw.text(L-0.5 , -0.5 , "L="+L);
		for(int R=1 ; R<=n ; ++R) StdDraw.text(-0.5 , R-0.5 , "R="+R);
		
		/*StdDraw.setPenRadius(0.004);
		for(int L=1 ; L<=n ; ++L) for(int R=L ; R<=n ; ++R) {
			StdDraw.filledCircle(L-0.5 , R-0.5 , 0.05);
			if(L<R) StdDraw.line(L-0.5 , R-0.5 , L+0.5 , R-0.5);
			if(R<n) StdDraw.line(L-0.5 , R-0.5 , L-0.5 , R+0.5);
		}*/
		
		StdDraw.setPenRadius(0.01);
		StdDraw.setPenColor(StdDraw.RED);
		for(int L=1 ; L<=n ; ++L) {
			for(int R=L ; R<=n ; ++R) {
				StdDraw.line(L-1 , R-1 , L , R-1);
				StdDraw.line(L , R-1 , L , R);
				//StdDraw.text(L-0.5 , R-0.5 , L*501+R+"");
			}
		}
		
		for(int i=0 ; i<m ; ++i) {
			int L = mi.nextInt();
			int R = mi.nextInt();
			char dir = mi.next().charAt(0);
			int c = mi.nextInt();
			if(dir=='L') {
				StdDraw.setPenColor(StdDraw.GREEN);
				StdDraw.line(L , R-1 , L , R);
				StdDraw.setPenColor(StdDraw.BLUE);
				StdDraw.text(L , R-0.5 , c+"");
			}else {
				StdDraw.setPenColor(StdDraw.GREEN);
				StdDraw.line(L-1 , R-1 , L , R-1);
				StdDraw.setPenColor(StdDraw.BLUE);
				StdDraw.text(L-0.5 , R-1 , c+"");
			}
			//System.out.println("Draw L="+L+" , R="+R+" , dir="+dir+" , c="+c+".");
		}
		
		/*StdDraw.setPenColor(StdDraw.PINK);
		while(true) {
			if(! StdDraw.isMousePressed())continue;
			StdDraw.point(StdDraw.mouseX() , StdDraw.mouseY());
		}*/
		
	}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值