CF1313D

题目

CF1313D Happy New Year

给定 \(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] 转移而来。

对于开始某个区间的转移,其中都要先记录下离散化后当前位置与下一个位置之间的长度 \(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;
}
posted @ 2022-05-18 13:16  AIskeleton  阅读(36)  评论(0编辑  收藏  举报