CF1313D

题目

CF1313D Happy New Year

给定 n,m,kn 个区间 [li,ri]
对于一个长为 m 的初始值为 0 的序列 a,在给定的 n 个区间中选择若干个,使序列 a 在区间 [li,ri] 之间的值 +1,其中保证任何时刻序列 a 中最大值小于 k
求问序列中最多奇数个数。

1n105,1m109,1k8

题解

思路:

首先注意到 k 的取值范围,可以想到要用状压
对于数据范围 1m109,显然 O(m2k) 的时间复杂度无法接受。
可以发现,n 个序列中不同的点最多只有 2×n 个,所以进行离散化。
奇偶数之间的转化可以用异或实现。
对于状态 s,可以通过 __builtin_parity() 函数快速求得其中有奇数个还是偶数个 1,返回值为 1 则个数为奇数个,这在 DP 转移时可以用到。

预处理:

应用差分的思想,对于一个区间 [li,ri],将其拆成 liri+1,分成开始目前区间和结束目前区间两种操作。
具体实现可以用 STL: vector <pair <int,int> >,分别存入 (li,i)(ri+1,i),按序列中位置排序。

DP

fi,j 表示离散化后序列第 i 个位置的状态为 j 时,第 i 个位置及其之前的序列中最多的奇数个数,初始值为极小值。
gi,j 表示在序列第 i 个位置时,第 j 个对其有影响的区间(没有就是 0)。

  • 如果当前操作是开始某个区间
    遍历数组 gi,找到第一个 0 的位置 p,此时 gi,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),即当前状态下增加的奇数个数。

  • 如果当前操作是结束某个区间
    遍历数组 gi,找到当前结束的区间的位置 p 并清空 gi,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(2n2k),所以要对空间进行优化。

  • 可以发现,对于开始某个区间的 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 的第一维省略掉,即设 fi 为状态为 i 时的当前位置前的奇数最大值,并对 DP 的转移略为改动:

  • 如果操作是开始区间,就逆向遍历所有状态:for(int i=(1<<k)-1;i>=0;i--),转移时也直接去掉第一维即可。
  • 如果操作是结束区间,就正向遍历所有状态,可以类比开始区间的操作。

最后的输出值是 f0,也就是序列离散化后最后一个位置在不受任何区间影响时的值。
时间复杂度 O(nlog2n+2k),空间复杂度 O(2k)
完结。

代码

#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 @   AIskeleton  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示