Codeforces Gym102059 G. Fascination Street【DP】

Codeforces Gym102059 G. Fascination Street【DP】

这是赛后补的题  找了一些题解发现下面这篇最好理解,我用我自己的理解再写一遍详细的题解:

这是原作者:
————————————————
版权声明:本文为CSDN博主「Gene_INNOCENT」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41552508/article/details/99082085

欢迎各位大佬指出错误,本人的第一篇题解

英文题面:

题目大意:

一条街上有N个点,需要从中选择一些点安装路灯,以照亮所有点,在一个点安装路灯可以照亮其左右点,每个点建立路灯都有对应的花费,并且你有K次机会交换某两个点建造路灯的花费,求整条路被照亮所需最小花费。(1≤N≤250000,0≤k≤9,0≤Wi≤1e9)

思路:

  题目看起来就感觉像是个DP,如果没有“可以交换两个点的花费K次”的条件,就完全可以入手,只需两维:即一维表示当前点i的位置即照亮前i个点,另一维表示当前点的前一点i-1以及当前点i的三种状态:(0:亮——不亮)(1:不亮——不亮)(2:亮/不亮——亮)。

  但是多了这个条件,如果按正常做法就会违背DP的一个必要条件:无后效性。

  所以这玩意到底是不是DP啊!

  是!不过得重新思考状态转移方程。既然其破坏了无后效性,那就把它整成满足无后效性。

  首先,交换有意义必须交换双方一个没灯一个有灯。

  其次,关于交换我们可以换着思考方式,不一定需要同时,我们可以先交后换,也可以先换了后交。什么意思呢,就是对于某个有路灯点a,我们先欠着不交,然后请后面某一不需要安装路灯的点b交b点的路灯费,也可以先交本来不需要路灯位置的路灯费,然后后面需要路灯的位置再欠路灯费,就相当与交换了。(核心)

  思路有了,所以该怎么实现呢?

  首先,我们构建DP状态,除了前面提到的两维,还需要加个两维,一位表示欠的路灯花费数,另一维表示还的路灯花费数的。即dp[i][j][k][l],i表示当前为第几个点,j表示欠了次,k表示还了几次,l表示i-1和i路灯的状态。状态转移见代码。

#include<bits/stdc++.h> 
using namespace std;
typedef long long ll;
#define N 250010
ll dp[N][10][10][3];
ll w[N];
ll n,K;
ll ans=100000000000000000;
solve()
{
    memset(dp,0x3f,sizeof(dp));
    dp[0][0][0][0]=0;
    for (int i=1;i<=n;i++)
        for (int j=0;j<=K;j++)
            for (int k=0;k<=K;k++)
            {
                dp[i][j][k][0]=dp[i-1][j][k][2];// 当前位(不亮——不亮) 只能由前一位的(不亮/亮——亮) 转移过来
                if (k>0)    
                    dp[i][j][k][0]=min(dp[i][j][k][0],dp[i-1][j][k-1][2]+w[i]);//和上一行一样只不过还掉一个花费
                dp[i][j][k][1]=dp[i-1][j][k][0];//当前位(不亮——不亮) 只能由前一位的(亮--不亮) 转移过来
                if (k>0)
                    dp[i][j][k][1]=min(dp[i][j][k][1],dp[i-1][j][k-1][0]+w[i]); //和上一行一样只不过还掉一个花费
                for (int l=0;l<=2;l++)
                    dp[i][j][k][2]=min(dp[i][j][k][2],dp[i-1][j][k][l]+w[i]);//当前位的(不亮/亮——亮)可以由前一位的任何一种状态转移过来
                if (j>0)
                    for (int l=0;l<=2;l++)
                        dp[i][j][k][2]=min(dp[i][j][k][2],dp[i-1][j-1][k][l]);//与上面一个循环一样不过多欠一个花费                    
            }
    for (int i=0;i<=K;i++)
        ans=min(ans,min(dp[n][i][i][0],dp[n][i][i][2]));//选择点亮了所有的n个点并且欠的和还的个数抵消的所有的值中的最小值 
    cout<<ans<<endl;
}

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);
    cin>>n>>K;
    for (int i=1;i<=n;i++) 
        cin>>w[i];
    solve();
    return 0;
} 

 

posted @ 2020-06-16 11:18  CimonHe  阅读(189)  评论(0)    收藏  举报