题解 CF1313D Happy New Year

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

\(m\) 很大,需要离散化。

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

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

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

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

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

转移分两类讨论。

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

    • 选:\(f_{i,j}=f_{i-1,j-(1<<id)}+len\cdot \operatorname{chk}(j)\)

    • 不选:\(f_{i,j}=f_{i-1,j}+len\cdot \operatorname{chk}(j)\)

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

    • 不删(不符合条件):\(f_{i,j}=-inf\)

    • 删:\(f_{i,j}=\max(f_{i-1,j},f_{i-1,j+(1<<k)})+len\cdot \operatorname{chk}(j)\)

复杂度 \(O(nk2^k)\),实际上达不到。

记得滚动数组。

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;
}

posted @ 2023-07-17 21:43  HQJ2007  阅读(42)  评论(0)    收藏  举报