Pinely Round 2 (Div. 1 + Div. 2) F. Divide, XOR, and Conquer(区间dp)

题目

给定长为n(n<=1e4)的数组,第i个数为ai(0<=ai<2的60次方)

初始时,区间为[1,n],也即l=1,r=n,

你可以在[l,r)中指定一个k,将区间分成左半边[l,k]、右半边[k+1,r]

1. 如果左半边异或和与异或和的异或和相等,则可以二选一,要么保留左半边,要么保留右半边

2. 否则,只能保留异或和大的那半边

当l=r时,游戏结束

对于每个i,判断是否能通过适当操作,使得游戏结束时l=r=i

实际t(t<=1e4)组样例,保证sumn不超过1e4

思路来源

力扣群 潼神

Pinely Round 2 (Div. 1 + Div. 2) F. Divide, XOR, and Conquer(区间dp)_第1张图片

题解

这个st[0]和ed[0]实际只需要占一位,分开写的话可读性会好一点

此处由于值域限制,直接维护在了st和ed的第60位

n=1e4,说明只能是O(1)转移的区间dp

异或和的两种情况:

1. [l,r]异或和为0,那么[l,x](xl)的区间都可以异或出

2. [l,r]异或和为s(s≠0),记s的最高位为b,

那么,如果[l,x](x

同理,如果[y,r](y>l)的异或和包含b这一位,[y,r]的异或和就一定大于[l,y-1]的异或和

判断

①左端点/右端点第60位打过标记,说明存在共左端点/右端点的更大的区间异或和为0

②[l,r]异或和为s,s和左端点/右端点的标记有交,说明存在共左端点/右端点的更大的区间的异或和的最高位能被s取到,也就是s比区间另一半大

设位

①如果异或和为0,在第60位打标记

②否则,在异或和最高位打标记

心得

本题是长区间向短区间下放,没怎么写过,但本身区间dp也很灵活

由于下放时一定需要固定一个端点,所以可以将信息维护在端点处供后续使用

也就只需要开一维,不像传统区间dp开两位数组那样了

__builtin_clzll(s)是获取64位数二进制前导0个数

63-__builtin_clzll(s)是获取64位数二进制最高位的1是第几位

从右往左,从第0位开始数,也就是1<

32位数时,可以对应改成__builtin_clz(s)、31-__builtin_clz(s)

代码

#include
using namespace std;
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define per(i,a,b) for(int i=(a);i>=(b);--i)
typedef long long ll;
typedef double db;
typedef pair P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<r)的异或和有0
	//之前的[L,r](L>B&1 || br[r]>>B&1)return 1;
	ll s=sum[r]^sum[l-1];
	return (s&bl[l]) || (s&br[r]);
	//[l,r]的异或和有[l,R](R>r)的异或和的最高位
	//[l,r]的异或和有[L,r](L

你可能感兴趣的:(#,区间dp,区间dp,异或)