Codeforces Round #622 (Div. 2) D.Happy New Year(状压dp)

题目

n(n<=1e5)个区间,第i个区间[li,ri](1<=li<=ri<=m,1<=m<=1e9),

保证区间内的每个端点不会被覆盖超过8次,选一些区间使得被覆盖的次数为奇数的点最多

输出最大的被覆盖的次数为奇数的点的个数

思路来源

https://codeforces.com/contest/1313/submission/71864317

https://www.bilibili.com/video/BV1Q7411M7S9?p=5

题解

首先离散化,压成2e5个左开右闭的区间,每个新区间单独考虑,

dp[i][S]表示第i个新区间,8个线段覆盖其的状态为S时,只考虑<=i的新区间时的最大点数

当前x区间选的线段可能和x-1区间选的线段有公有的部分,

如果有一些线段同时覆盖了x-1和x的话,就要么都取,要么都不取

那dp[i][S]表示到第i个区间,第i个区间覆盖状态是S的时候,

如果S包含的x和x-1的公有状态是T,dp[i][S]只能从dp[i-1][T的超集]转移而来,

先把T的超集的答案缩到T更新T,再把S缩到T,用T更新S,思路大致如此

 

但是用tmp数组做跳板的时候,二者下标不一致,即公有状态T在seg[i-1]的下标是j,在seg[i]的下标是k,

这里就新开两个vector,使其pre[l]为seg[i-1]中的j,now[l]为seg[i]中的k,

这样T的超集中出现了第j位的时候,对tmp的第l位赋值,

S出现了第k位的时候,就从tmp含l的位进行转移,这样下标就一致了

 

至于考虑x的时候,为什么不用考虑x-2及更小的区间与x的一致性,

是x-1这个区间已经和前面一致了,所以x和x-1保持一致就可以了,

类似扫描线,不可能断的

代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=1e5+10,M=2e5+10,S=1<<8;
int n,m,k,l,r,x[M],c;
int dp[M][S],tmp[S],bit[S],ans;
vector<int>seg[M];
struct line{
    int l,r;
}e[N];
int main(){
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<S;++i){
        bit[i]=(bit[i>>1]+(i&1))%2;
    }
    for(int i=1;i<=n;++i){
        scanf("%d%d",&l,&r);
        r++;
        e[i]={l,r};
        x[++c]=l;x[++c]=r;
    }
    sort(x+1,x+c+1);
    c=unique(x+1,x+c+1)-(x+1);
    for(int i=1;i<=n;++i){
        e[i].l=lower_bound(x+1,x+c+1,e[i].l)-x;
        e[i].r=lower_bound(x+1,x+c+1,e[i].r)-x;
        for(int k=e[i].l;k<e[i].r;++k){
            seg[k].pb(i);
        }
    }
    for(int i=1;i<c;++i){
        int len=x[i+1]-x[i];
        vector<int>pre,now;
        for(int j=0;j<seg[i-1].size();++j){
            for(int k=0;k<seg[i].size();++k){
                if(seg[i-1][j]==seg[i][k]){
                    pre.pb(j);
                    now.pb(k);
                }
            }
        }
        //上一个线段的第j条 对应这一个线段的第k条 为了tmp数组一致 都对应到pre[l] now[l]的第l位
        memset(tmp,0,sizeof tmp);
        int up1=1<<seg[i-1].size(),up2=1<<seg[i].size();
        for(int j=0;j<up1;++j){
            int st=0;
            for(int k=0;k<pre.size();++k){
                if(j>>pre[k]&1){
                    st|=(1<<k);
                }
            }
            tmp[st]=max(tmp[st],dp[i-1][j]);
        }
        for(int j=0;j<up2;++j){
            int st=0;
            for(int k=0;k<now.size();++k){
                if(j>>now[k]&1){
                    st|=(1<<k);
                }
            }
            dp[i][j]=max(dp[i][j],tmp[st]+bit[j]*len);//注意到up2最小为1 故dp[i][st]为考虑<=i的状态时的最优
        }
    }
    int up2=1<<seg[c-1].size();
    for(int i=0;i<up2;++i){
        ans=max(ans,dp[c-1][i]);
    }
    printf("%d\n",ans);
    return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值