题目
给定长为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文章来源:https://www.toymoban.com/news/detail-698859.html
32位数时,可以对应改成__builtin_clz(s)、31-__builtin_clz(s)文章来源地址https://www.toymoban.com/news/detail-698859.html
代码
#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;
}
到了这里,关于Pinely Round 2 (Div. 1 + Div. 2) F. Divide, XOR, and Conquer(区间dp)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!