no crossing(代码源打卡day.2)

随便说说

昨天集训队开省赛 V P VP VP,写完每日一题已经很晚了,今天一早来补一下。

题意

在一条数轴上有很多条单向路径,每条单向路径都有一个对应的权值。有一个人现在从数轴上任意一点出发,他经过且仅经过 k k k个点,要求当他经过一条路径时候不能越过之前已经经过过的点,请问他经过的路径的权值最小是多少?

题解

先说一下杜老师的思路。我们会发现因为不能越过已经越过的点,所以我们最后能够选择的路径长度是越来越短的,那么我们其实就可以利用区间 d p dp dp将小区间转移出来来得出最后答案,可惜代码能力太差最后还是没 A C AC AC

我的思路

其实也不算事我的思路,因为写不出题只能看大佬的代码,慢慢摸出来的大致意思(苣蒻石锤)。

关于 d p dp dp

这边先说一下我对 d p dp dp的一些理解。 d p dp dp的本质是将子集合合并为一个集合,集合内的数据必然有其相似性才会被放到一个集合中。这么说吧,所有的 d p dp dp问题我们都能够用爆搜来解决,其中的本质就是将所有的状态一个一个找出来然后找到最符合答案的那个状态然后输出,但这就会出现一个问题——时间超限。时间超限的原因是状态数太多导致我们无法在有限的时间内完全枚举,那么我们就可以想到,其实有一些状态是有一定的相似性的,比如他们都会在某一个时间点出现在某一个位置,而另外不相似的状态与我们的答案并没有直接关系。(比如我们写记搜时候我们只会关注接下来我们要走过的路程而不会注意我们之前走过的路程)所以我们只要提取出来和答案相关的状态而不去考虑与答案不相关的状态就能列出 d p dp dp数组。

写到这里感觉还是多少有点抽象的。什么是和答案有关的状态?什么是和答案无关的状态?列出 d p dp dp数组之后我们要怎么继续列出 d p dp dp方程。

其中的一些我仍然无法解答,可能还是要通过不断的刷题来巩固,至于 d p dp dp方程,我后面有时间会整理一下 y x c yxc yxc大佬讲过的闫氏 d p dp dp分析法。

正文

首先我们先列出需要的 d p dp dp数组 d p [ t ] [ l ] [ r ] [ 0 / 1 ] dp[t][l][r][0/1] dp[t][l][r][0/1],分别表示经过了几个点,现在的允许下一个点能够到达的左右区间,以及当前节点在左/右区间。

先想一下如果这道题要你来爆搜你会怎么做。遍历所有点作为起点,然后 d f s dfs dfs,记录下经过的路径以及经过了几个点,现在的权值和,现在在哪个点,然后遍历与当前点相邻的节点如果没有越过其他的节点我们就能够遍历它,如果已经遍历了 k k k个就能返回答案了。那有哪些数据是我们不关注的或者说和最后答案没有关系的?我们看一下处理过程要求不越过其他的点,其实我们只要不越过相邻的点,也就是比 x x x大的第一个数以及比 x x x小的第一个数,我们可以将这个区间作为我们的左右区间,其余经过了几个点,现在的权值和以及在哪个点。这样的话这样的话我们就要记录四个数据——当前点,左右边界以及经过点的数量,不用说这个 d p dp dp数组是不是有点呆,光就是空间都要炸。我们还需要优化。一个完整的区间加上一个中间节点其实我们可以划分为两个区间——左边界和中间点为一个区间,右边界和中间点一个区间,并且标记当前节点是在左边界还是在右边界,用两个 d p dp dp数组来存储信息以便消除冗余。

至此我们已经能够推出上面的 d p dp dp数组 d p [ t ] [ l ] [ r ] [ 0 / 1 ] dp[t][l][r][0/1] dp[t][l][r][0/1]——节点数量,左边界,右边界,在左/右边界上。

然后是 d p dp dp运算,我们如何求出 d p dp dp数组?

先给出一个数组 d p [ t ] [ l ] [ r ] [ 0 / 1 ] dp[t][l][r][0/1] dp[t][l][r][0/1],我们关注它这个数组要怎么转移出去?之前说过我们不能越过左右边界,那我们只能枚举区间内的点,由左右边界分别转移到中间的点上,分别计算转移的代价 m i n ( d p [ t ] [ l ] [ r ] [ 0 ] + w [ l ] [ m i d ] , d p [ t ] [ l ] [ r ] [ 1 ] + w [ r ] [ m i d ] ) min(dp[t][l][r][0]+w[l][mid],dp[t][l][r][1]+w[r][mid]) min(dp[t][l][r][0]+w[l][mid],dp[t][l][r][1]+w[r][mid])。同时因为区间会被我们一分为二所以我们转移的对象有两个分别为 d p [ t + 1 ] [ l ] [ m i d ] [ 1 ] dp[t+1][l][mid][1] dp[t+1][l][mid][1]以及 d p [ t + 1 ] [ m i d ] [ r ] [ 0 ] dp[t+1][mid][r][0] dp[t+1][mid][r][0]最后得出答案。

int mp[N][N];
int dp[K][N][N][2];//经过的点的数量 左端点 右端点 在左/右端点
int n,m,k;
void INIT(){
    memset(dp,0x3f,sizeof dp);
    memset(mp,0x3f,sizeof mp);
    return ;
}
void MAIN(){
    cin>>n>>k>>m;
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        mp[u][v]=min(mp[u][v],w);
    }
    for(int i=1;i<=n;i++) dp[1][0][i][1]=dp[1][i][n+1][0]=0;
    for(int t=2;t<=k;t++){//经过了t个点
        for(int l=0;l<=n-1;l++){//左端点
            for(int r=l;r<=n+1;r++){//右端点
                for(int mid=l+1;mid<=r-1;mid++){//转移对象
                    int mini=min(dp[t-1][l][r][0]+mp[l][mid],dp[t-1][l][r][1]+mp[r][mid]);
                    dp[t][mid][r][0]=min(dp[t][mid][r][0],mini);
                    dp[t][l][mid][1]=min(dp[t][l][mid][1],mini);
                }
            }
        }
    }
    int ans=inf;
    for(int i=0;i<=n;i++) for(int j=i;j<=n+1;j++) ans=min(ans,min(dp[k][i][j][0],dp[k][i][j][1]));
    cout<<(ans==inf?-1:ans)<<endl;
    return ;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值