Codeforces Round #600 (Div. 2) E

题意

n n n个广播,每个广播的范围可以通过一个花费扩大 1 1 1,求最小花费覆盖。

题解

d p dp dp
首先我们可考虑倒着 d p dp dp,正着也行。
d p [ i ] dp[i] dp[i]表示覆盖 i → m i \to m im的最小花费。
1.如果当前已经被覆盖,显然答案是 d p [ i ] = d p [ i + 1 ] dp[i]=dp[i+1] dp[i]=dp[i+1]
2.如果在某个广播中心左侧,答案是 d p [ i ] = d p [ L [ j ] − i + R [ i ] + 1 ] + L [ j ] − i dp[i]=dp[L[j]-i+R[i]+1]+L[j]-i dp[i]=dp[L[j]i+R[i]+1]+L[j]i
【当然下标有可能超过 m m m,取最小值即可。
3.如果在右侧显然没有任何意义,因为接下来我们干的事会完全涵盖这种情况(反之却不可以)。
但是我们可以发现这样有一个弊端,就是比如, d p [ 3 ] = 5 dp[3]=5 dp[3]=5 d p [ 4 ] = i n f dp[4]=inf dp[4]=inf
但是显然 d p [ 4 ] = d p [ 3 ] dp[4]=dp[3] dp[4]=dp[3]是没有任何问题的。

所以我们可以采取,线段树来维护最小值,情况二 d p [ i ] = d p [ x ] + k dp[i]=dp[x]+k dp[i]=dp[x]+k d p [ x ] dp[x] dp[x]直接求很有可能是 i n f inf inf,见上述例子,所以访问 m i n d p [ i ≥ x ] mindp[i \geq x] mindp[ix],即可。

或者采用比较神仙的一种做法,能够保证复杂度在 n m nm nm
首先我们 ≤ 0 o r ≥ m + 1 \leq0or\geq m+1 0orm+1处放多少个广播绝对不会影响最后结果,因为他们导致的最优结果只能是 m m m,而里面的广播最坏结果是 m − 1 m-1 m1

但是设置一个 m + 1 m+1 m+1处的广播有什么好处呢,我们再考虑刚刚那个例子,显然 d p [ i ] = m − i + 1 dp[i]=m-i+1 dp[i]=mi+1(根据这个广播),正好能够处理这种情况。因为刚好加上这部分差等价于直接覆盖到底。

那再往前推也会有类似的例子,并且不只是加到底这么简单了,而可能加到下一个广播的最优区域外,这个时候前面的最优情况还没有迭代到,就需要借用下一个的来补上(实际想表达的是由前面的加过来,但是可以用后一个补上来保证复杂度。并且贡献是等价的。)

举个例子。两个广播 [ 3 , 0 ] , [ 7 , 0 ] [3,0],[7,0] [3,0],[7,0], m = 9 m=9 m=9
显然 d p [ 5 ] = 2 dp[5]=2 dp[5]=2,如果按之前后面,大于 5 5 5的都是 i n f inf inf,但实际上他们也应该是2。这里我有一个更大的广播比如 [ 4 , 0 ] [4,0] [4,0],忽略 [ 3 , 0 ] [3,0] [3,0],处理到 d p [ 1 ] = d p [ 8 ] + 3 dp[1]=dp[8]+3 dp[1]=dp[8]+3, d p [ 8 ] dp[8] dp[8]应该是多少呢,显然应该是 1 1 1而不是 2 2 2,看起来是之前构成的,其实是 4 4 4这个广播继续扩大了一下。
回过头来考虑 3 3 3 d p [ 1 ] = d p [ 6 ] + 2 = d p [ 8 ] + 3 = 1 + 3 = 4 dp[1]=dp[6]+2=dp[8]+3=1+3=4 dp[1]=dp[6]+2=dp[8]+3=1+3=4

当然忽略 3 3 3,加入一个 [ 2 , 0 ] [2,0] [2,0]
d p [ 1 ] = d p [ 4 ] + 1 dp[1]=dp[4]+1 dp[1]=dp[4]+1,这个 4 4 4位置如何处理呢,显然是 d p [ 4 ] = d p [ 10 ] + 3 dp[4]=dp[10]+3 dp[4]=dp[10]+3
如果前面的位置更大点,最后理应是 2 2 2的这个广播扩大这么大,但是计算 4 4 4的时候是以 7 7 7扩大这么大计算的,不过可以直接继承过去,等价于先从那里借的。以后还回去。

没试过数据结构,不过貌似 n 2 m n^2m n2m都能过,直接数据结构可能更方便。

#include<bits/stdc++.h>
#define FOR(i,l,r) for(int i=l;i<=r;i++)
#define sf(x) scanf("%d",&x)
using namespace std;

const int maxn = 2e5+500;
typedef long long ll;

int n,m;
int dp[maxn];
int L[maxn],R[maxn];

int main(){
    cin>>n>>m;
    FOR(i,1,n){
        int x,s;
        sf(x),sf(s);
        L[i]=max(1,x-s);
        R[i]=min(x+s,m);
    }
    memset(dp,0x3f,sizeof(dp));
    dp[m+1]=0;
    for(int i=1;i<=m;i++)dp[i]=m-i+1;
    for(int i=m;i>=1;i--){
        for(int j=1;j<=n;j++){
            if(L[j]<=i&&i<=R[j]){
                dp[i]=min(dp[i],dp[i+1]);
            }
            else if(i<L[j]){
                dp[i]=min(dp[i],dp[min(R[j]+L[j]-i,m)+1]+L[j]-i);
            }
        }
    }
    cout<<dp[1]<<endl;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值