题解 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\) 表示当前点对应的实际长度。
转移分两类讨论。
-
新遍历到了一个区间:
-
选:\(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)\)
-
-
遍历到了一个区间的结尾,需要删除:
-
不删(不符合条件):\(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;
}

浙公网安备 33010602011771号