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

思路来源

力扣群 潼神

题解

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

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

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

异或和的两种情况:

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

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

那么,如果[l,x](x<r)的异或和包含b这一位,[l,x]的异或和就一定大于[x+1,r]的异或和

同理,如果[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<<b中的b,不存在时为-1

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

代码

cpp 复制代码
#include<bits/stdc++.h>
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<ll,int> P;
#define fi first
#define se second
#define pb push_back
#define dbg(x) cerr<<(#x)<<":"<<x<<" ";
#define dbg2(x) cerr<<(#x)<<":"<<x<<endl;
#define SZ(a) (int)(a.size())
#define sci(a) scanf("%d",&(a))
#define pt(a) printf("%d",a);
#define pte(a) printf("%d\n",a)
#define ptlle(a) printf("%lld\n",a)
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef unsigned ui;
//typedef __uint128_t L;
typedef unsigned long long L;
typedef unsigned long long ull;
const int N=1e4+10,B=60;//xor=0代表的位
int t,n;
ll v,bl[N],br[N],sum[N];
char ans[N];
bool cal(int l,int r){
	if(l==1 && r==n)return 1;
	//之前的[l,R](R>r)的异或和有0
	//之前的[L,r](L<l)的异或和有0
	if(bl[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<l)的异或和的最高位
}
void op(int l,int r){
	ll s=sum[r]^sum[l-1];
	int b;
	if(!s)b=B;	// 当前[l,r]的异或和有0 
	else b=63-__builtin_clzll(s); // 当前[l,r]的异或和的最高位
	bl[l]|=1ll<<b;
	br[r]|=1ll<<b;
}
int main(){
	sci(t);
	while(t--){
		sci(n);
		rep(i,1,n){
			scanf("%lld",&v);
			sum[i]=sum[i-1]^v;
			bl[i]=br[i]=0;
			ans[i]='0';
		}
		per(sz,n,1){
			rep(l,1,n+1-sz){
				int r=l+sz-1;
				//printf("l:%d r:%d ok:%1d s:%lld b:%d\n",l,r,cal(l,r),sum[r]^sum[l-1],63-__builtin_clzll(sum[r]^sum[l-1]));
				if(cal(l,r)){
					op(l,r);
					if(l==r)ans[l]='1';
				}
			}
		}
		ans[n+1]='\0';
		printf("%s\n",ans+1);
	}
	return 0;
}
相关推荐
Jcqsunny12 天前
[dp]答疑
c++·算法·动态规划·dfs·区间dp
爱跑步的程序员~3 个月前
1312. 让字符串成为回文串的最少插入次数
leetcode·动态规划·区间dp
硕风和炜3 个月前
【LeetCode:312. 戳气球+ 动态规划】
java·算法·leetcode·缓存·动态规划·递归·区间dp
zaiyang遇见4 个月前
Equal XOR(异或,思维)
算法·c++11·区间·c/c++·异或·构造·信息学奥赛
闻缺陷则喜何志丹5 个月前
【动态规划 区间dp 位运算】100259. 划分数组得到最小的值之和
c++·算法·动态规划·力扣·位运算·区间dp·最小
顾客言6 个月前
牛客练习赛122
算法·区间dp
Tisfy1 年前
LeetCode 260. 只出现一次的数字 III:异或
算法·leetcode·题解·位运算·异或
感觉画质不如…原神1 年前
Leetcode.664 奇怪的打印机
区间dp