CF1313D
题目
给定 \(n,m,k\) 和 \(n\) 个区间 \(\left[ l_i,r_i \right]\)。
对于一个长为 \(m\) 的初始值为 \(0\) 的序列 \(a\),在给定的 \(n\) 个区间中选择若干个,使序列 \(a\) 在区间 \(\left[ l_i,r_i \right]\) 之间的值 \(+1\),其中保证任何时刻序列 \(a\) 中最大值小于 \(k\)。
求问序列中最多奇数个数。
\(1 \le n \le 10^5, 1 \le m \le 10^9,1 \le k \le 8\)。
题解
思路:
首先注意到 \(k\) 的取值范围,可以想到要用状压。
对于数据范围 \(1 \le m \le 10^9\),显然 \(O(m\, 2^k)\) 的时间复杂度无法接受。
可以发现,\(n\) 个序列中不同的点最多只有 \(2 \times n\) 个,所以进行离散化。
奇偶数之间的转化可以用异或实现。
对于状态 \(s\),可以通过 __builtin_parity()
函数快速求得其中有奇数个还是偶数个 \(1\),返回值为 \(1\) 则个数为奇数个,这在 DP 转移时可以用到。
预处理:
应用差分的思想,对于一个区间 \(\left[ l_i,r_i \right]\),将其拆成 \(l_i\) 和 \(r_i+1\),分成开始目前区间和结束目前区间两种操作。
具体实现可以用 STL: vector <pair <int,int> >
,分别存入 \(\left( l_i,i \right)\) 和 \(\left( r_i+1,-i \right)\),按序列中位置排序。
DP:
设 \(f_{i,j}\) 表示离散化后序列第 \(i\) 个位置的状态为 \(j\) 时,第 \(i\) 个位置及其之前的序列中最多的奇数个数,初始值为极小值。
设 \(g_{i,j}\) 表示在序列第 \(i\) 个位置时,第 \(j\) 个对其有影响的区间(没有就是 \(0\))。
- 如果当前操作是开始某个区间
遍历数组 \(g_i\),找到第一个 \(0\) 的位置 \(p\),此时 \(g_{i,p}\) 即当前区间是第 \(p\) 个对位置 \(i\) 有影响的区间。- 如果状态 \(j\) 的第 \(p\) 位是 \(1\),即状态 \(j\) 下加入了当前区间,则
f[i][j]
应由f[i-1][j^(1<<p)]
转移而来。 - 如果状态 \(j\) 的第 \(p\) 位是 \(0\),即状态 \(j\) 下未加入当前区间,则
f[i][j]
应由f[i-1][j]
转移而来。
- 如果状态 \(j\) 的第 \(p\) 位是 \(1\),即状态 \(j\) 下加入了当前区间,则
对于开始某个区间的转移,其中都要先记录下离散化后当前位置与下一个位置之间的长度 \(len\)(最后一个位置为 \(0\)),转移时加上 len*__builtin_parity(j)
,即当前状态下增加的奇数个数。
- 如果当前操作是结束某个区间
遍历数组 \(g_i\),找到当前结束的区间的位置 \(p\) 并清空 \(g_{i,p}\),具体原因可以结合前文理解。- 如果状态 \(j\) 的第 \(p\) 位是 \(1\),即状态 \(j\) 下依然存在当前区间,可知状态 \(j\) 不合法,所以赋极小值。
- 如果状态 \(j\) 的第 \(p\) 位是 \(0\),即状态 \(j\) 下已不存在当前区间,则
f[i][j]
应由两个合法状态f[i-1][j]
和f[i-1][j^(1<<p)]
中的较大值转移而来。
因为当前状态的前一步状态可以本来就没有当前区间即f[i-1][j]
,也可以是在位置 \(i\) 时去掉了当前区间即f[i-1][j^(1<<p)]
。所以取较大值转移。
对于结束某个区间的转移,如果情况合法,也要在转移时加上 len*__builtin_parity(j)
。
优化:
此时的空间复杂度达到了 \(O(2n2^k)\),所以要对空间进行优化。
- 可以发现,对于开始某个区间的 DP 转移,在
f[i][j]
由f[i-1][j^(1<<p)]
转移而来的情况下,j
一定大于j^(1<<p)
。 - 可以发现,对于结束某个区间的 DP 转移,在
f[i][j]
由f[i-1][j]
和f[i-1][j^(1<<p)]
中的较大值转移而来的情况下,j
一定小于j^(1<<p)
。
所以可以将数组 \(f\) 的第一维省略掉,即设 \(f_i\) 为状态为 \(i\) 时的当前位置前的奇数最大值,并对 DP 的转移略为改动:
- 如果操作是开始区间,就逆向遍历所有状态:
for(int i=(1<<k)-1;i>=0;i--)
,转移时也直接去掉第一维即可。 - 如果操作是结束区间,就正向遍历所有状态,可以类比开始区间的操作。
最后的输出值是 \(f_0\),也就是序列离散化后最后一个位置在不受任何区间影响时的值。
时间复杂度 \(O(n\log_2n+2^k)\),空间复杂度 \(O(2^k)\)
完结。
代码
#include <bits/stdc++.h>
#define pb push_back
using namespace std;int rd(){
int w=0,v=1;char c=getchar();while(c<'0'||c>'9'){if(c=='-')v=-1;c=getchar();}
while(c>='0'&&c<='9'){w=(w<<1)+(w<<3)+(c&15);c=getchar();}return w*v;
}const int N=8;int n,k,f[1<<N],g[N];vector <pair <int,int> > a;
signed main(){
n=rd(),rd(),k=rd();for(int i=1,l,r;i<=n;i++)l=rd(),r=rd(),a.pb({l,i}),a.pb({r+1,-i});
sort(a.begin(),a.end());for(int i=1;i<(1<<k);i++)f[i]=-0x7f;
for(int v=0,l,b,p;v<a.size();v++){
b=a[v].second;if(v==a.size()-1)l=0;else l=a[v+1].first-a[v].first;
if(b>0){
for(int i=0;i<k;i++)if(!g[i]){g[p=i]=b;break;}
for(int i=(1<<k)-1;i>=0;i--)
if(i&(1<<p))f[i]=f[i^(1<<p)]+l*__builtin_parity(i);
else f[i]+=l*__builtin_parity(i);
}else{
for(int i=0;i<k;i++)if(g[i]==-b){g[p=i]=0;break;}
for(int i=0;i<(1<<k);i++)
if(i&(1<<p))f[i]=-0x7f;
else f[i]=max(f[i],f[i^(1<<p)])+l*__builtin_parity(i);
}
}cout<<f[0]<<endl;return 0;
}