题解 CF1313D Happy New Year

带有小 trick 的 DP,长知识了。

m 很大,需要离散化。

为了方便,采用扫描线的方式,不对其进行实际意义上的离散,而是对于第 i 个区间 [l,r],插入 (l,i),(r+1,i) 两个 pair,最后排个序。这样相邻两个 pair 之间的部分就缩成了一个点。

同时我们还发现 k8,也就是说经过每个点的区间个数不超过 8。于是在遍历每一个点的时候,我们记录当前有哪些区间经过它,并给它们编号(小于等于 8)。

定义 fi,j 表示已经遍历到第 i 个点,经过点 i 的所有区间中被选的状态为 j,最大的快乐值。

定义 chk(j) 表示集合 j 中有奇数个元素还是偶数个元素。如果是奇数个,返回 1;否则返回 0

定义 len 表示当前点对应的实际长度。

转移分两类讨论。

  1. 新遍历到了一个区间:

    • 选:fi,j=fi1,j(1<<id)+lenchk(j)

    • 不选:fi,j=fi1,j+lenchk(j)

  2. 遍历到了一个区间的结尾,需要删除:

    • 不删(不符合条件):fi,j=inf

    • 删:fi,j=max(fi1,j,fi1,j+(1<<k))+lenchk(j)

复杂度 O(nk2k),实际上达不到。

记得滚动数组。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,inf=INT_MAX>>1;
int n,m,k,vis[10],f[N];
pair<int,int>a[N*2];
inline int chk(int x){
  int cnt=0;
  while(x){x-=x&(-x);++cnt;}
  return cnt&1;
}
int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n>>m>>k;
  for(int i=1;i<=n;++i){
    int l,r;cin>>l>>r;
    a[i]=make_pair(l,i);a[i+n]=make_pair(r+1,-i);
  }
  sort(a+1,a+2*n+1);
  for(int i=1;i<=256;++i)f[i]=-inf;
  for(int i=1;i<=2*n;++i){
    int t=a[i].second,k,len=(i==2*n?0:a[i+1].first-a[i].first);
    if(t>0){
      for(int j=0;j<8;++j)if(!vis[j]){vis[j]=t;k=j;break;}
      for(int j=255;j;--j){
        if((j>>k)&1)f[j]=f[j-(1<<k)]+len*chk(j);
        else f[j]+=len*chk(j);
      }
    }else{
      for(int j=0;j<8;++j)if(vis[j]==-t){vis[j]=0;k=j;break;}
      for(int j=0;j<256;++j){
        if((j>>k)&1)f[j]=-inf;
        else f[j]=max(f[j],f[j+(1<<k)])+len*chk(j);
      }
    }
  }
  cout<<f[0]<<endl;
  return 0;
}

本文作者:HQJ2007

本文链接:https://www.cnblogs.com/HQJ2007/p/17561343.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   HQJ2007  阅读(28)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起