NRE AtCoder Regular Contest 085

F - NRE

每次对于一个区间 [l,r] ,可以选择使用或者不使用,这里可以联系到动态规划的方向上

由于这些区间是可以重合的,叠在一起相对复杂,能不能只去关注不被覆盖的部分?(这一步在简化价值计算)
对a和b的搭配情况,有以下:
a        1        1        0        0

b        1        0        1        0

一开始让a的每一个元素都为1,那么之后,如果当b[i]=1时,将a[i]改成0,则(a,b)组合由(1,1)变成(0,1),价值增加了1,当b[i]=0时,将a[i]改成0,由(1,0)变成(0,0)价值减少了1

所以可以只关注a的不被覆盖的部分

将区间按照左端点从小到大排序,设计状态dp[i][j](j>=i)表示前1~i个点a的值已经确定,第i+1到第j个点为1的情况下,如果j=i,那么第i个点后没有连续的1的段,前1~i个点贡献的价值

遍历i从0到n-1,用dp[i][...]对dp[i+1][...]进行更新

先看在i+1的位置上不覆盖区间[ l = i+1,r ]的情况:如果i+1的位置上是1,这里的1来自之前的覆盖

dp[i+1][j]=dp[i][j] j>=i+1 (1)

如果i+1的位置上是0:

dp[i+1][i+1]=min(dp[i+1][i+1] , dp[i][i] + cost[i+1]) (2)

再看i+1的位置上覆盖区间[ l = i+1 , r]的情况:
dp[i+1 , r]=min (dp[i,j]) i<=j<=r (3)

这里要快速找出dp[i][范围]内的最小值,可以使用线段树的数据结构

查看以上的状态转移方程,发现(1)其实是dp[i+1][j]继承了dp[i][j]

(2)是dp[i+1][i+1]被dp[i][i]更新

(3)是dp[i+1][从i+1到更大的数]被dp[i][从i到更大的数]更新

根据dp数组第二个维度被更新的顺序,可以将dp数组的第一维省去

#include<bits/stdc++.h>
using namespace std;
using ll=long long ;

const ll maxn=200000+5,inf=400000;

ll dp[maxn],mi[maxn<<2],b[maxn],cost[maxn];
ll n,q;

struct Seg{
    ll l,r;
    bool operator < (const Seg &rhs) const {
        if(l==rhs.l) return r>rhs.r;
        return l<rhs.l;
    }
}seg[maxn];

inline ll ls(ll x) {return x<<1;}
inline ll rs(ll x) {return (x<<1)|1;}

void push_up(ll p,ll pl,ll pr){
    mi[p]=min(mi[ls(p)],mi[rs(p)]);
}

void build_tree(ll p,ll pl,ll pr){
    if(pl==pr){
        mi[p]=inf;
        if(pl==0) mi[p]=0;
        return ;
    }
    ll mid=(pl+pr)>>1;
    build_tree(ls(p),pl,mid);
    build_tree(rs(p),mid+1,pr);
    push_up(p,pl,pr);
}

void update(ll p,ll pl,ll pr,ll L,ll R,ll v){
    if(pl>=L && pr<=R){
        mi[p]=v;
        return;
    }
    ll mid=(pl+pr)>>1;
    if(L<=mid) update(ls(p),pl,mid,L,R,v);
    if(R>mid) update(rs(p),mid+1,pr,L,R,v);
    push_up(p,pl,pr);
}

ll query(ll p,ll pl,ll pr,ll L,ll R){
    if(pl>=L && pr<=R){
        return mi[p];
    }
    ll res=inf;
    ll mid=(pl+pr)>>1;
    if(L<=mid) res=min(res,query(ls(p),pl,mid,L,R));
    if(R>mid) res=min(res,query(rs(p),mid+1,pr,L,R));
    return res;
}


int main()
{
    ios::sync_with_stdio(0);cin.tie(0);

    cin>>n;
    for(ll i=1;i<=n;i++) {
        cin>>b[i];
        if(b[i]==1) cost[i]=1;
        else cost[i]=-1;
    }
    cin>>q;
    for(ll i=1;i<=q;i++){
        cin>>seg[i].l>>seg[i].r;
    }
    stable_sort(seg+1,seg+1+q);
    build_tree(1,0,n);


    for(ll i=0;i<=n;i++) dp[i]=inf;
    dp[0]=0;

    //printf("%lld!!\n",query(1,0,n,0,1));

    ll cur=1;   //当前处理到的[l,r]区间
    for(ll i=0;i<n;i++){
        while(cur<=q && seg[cur].l==i+1){
            ll t=query(1,0,n,i,seg[cur].r);
            if(t<dp[seg[cur].r]){
                dp[seg[cur].r]=t;
                update(1,0,n,seg[cur].r,seg[cur].r,t);
            }
            cur++;
        }

        if(dp[i]+cost[i+1]<dp[i+1]){
            dp[i+1]=dp[i]+cost[i+1];
            update(1,0,n,i+1,i+1,dp[i+1]);
        }
        /*
        for(ll j=i+1;j<=n;j++){
            printf("dp[%lld][%lld]=%lld\n",i+1,j,dp[j]);
        }
        printf("\n\n");*/
    }

    ll base=0;
    for(ll i=1;i<=n;i++) {
        if(b[i]==0) base++;
    }
    cout<<dp[n]+base<<"\n";
    return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值