2022实战练习III 解题报告
2022实战练习 III
C. Xor
思路
由于前\(30 \%\)的数据中\(n \leq 20\),故可以考虑0-1穷举
而\(100\%\)数据中\(n \leq 32\),这是一个较为敏感的数据范围,因为一般来讲这种数据下朴素的0-1穷举会TLE,但这种数据只要规模折半就不会。同时本题中的区间异或和与加和都可以转化成两个小区间的异或和和加和,故考虑使用Meet In the Middle优化0-1穷举
更准确地,先维护前\(\displaystyle\left\lfloor\frac{n}{2}\right\rfloor\)个数的所有子序列的异或和与加和,用异或和在Trie中创建路径,并在末尾记录加和。然后计算后\(\displaystyle n-\left\lfloor\frac{n}{2}\right\rfloor\)个数的子序列的异或和和加和,在Trie中查找能使异或\(\leq m\)且加和最大的路径,更具体地,先通过类似树形DP的思想求出Trie上每一个节点为根的子树的加和最大值,假设考虑第\(k\)位,异或和的第\(k\)位为\(1\),\(m\)的第\(k\)位也为\(1\),则若Trie中当前节点的左孩子(即与查找的异或和搭配的数此位选\(0\))非空,则令以其为根的子树的加和最大值为备选答案,同时深搜下降以保证最终异或和不超过\(m\),但若Trie中当前节点不存在左孩子,即与查找的异或和搭配的数此位只能选\(1\),则此时异或后该位便小于\(m\)的对应位,此时无需深搜下降确定合法,直接返回结果,其他情况同理(详见程序)。最后用查找到的加和与当前选择的加和相加即为备选答案,输出备选答案中的最大值即可。
注意上述策略基于高位对答案的贡献远大于低位的特点,故一定要从高位向低位考虑!!!
AC代码
#include <bits/stdc++.h>
#define ifile(x) freopen(x,"r",stdin)
#define ofile(x) freopen(x,"w",stdout)
#define iofile(x,y) freopen(x,"r",stdin),freopen(y,"w",stdout)
#define int long long
#define INT LLONG
// #define LOCAL
#ifndef LOCAL
#define cin fin
#define cout fout
#endif
using namespace std;
ifstream fin("xor.in");
ofstream fout("xor.out");
int n,m,A[35],B[35];
struct node
{
node *lft=nullptr,*rgt=nullptr;
int xorval=0,addval=0;
};
node *root;
vector <node*> ptr;
void insert(int xorval,int addval,int pos,node *now)
{
if(pos==-1)//第0位到第31位遍历挂掉结果第31位到第0位A了
{
now->xorval=xorval;
now->addval=max(now->addval,addval);
return;
}
if(xorval&(1<<pos))
{
if(now->rgt==nullptr)
{
now->rgt=new node;
ptr.emplace_back(now->rgt);
}
insert(xorval,addval,pos-1,now->rgt);
}
else
{
if(now->lft==nullptr)
{
now->lft=new node;
ptr.emplace_back(now->lft);
}
insert(xorval,addval,pos-1,now->lft);
}
return;
}
void calc(node *now)
{
if(now==nullptr)
{
return;
}
if(now->lft==nullptr&&now->rgt==nullptr)
{
return;
}
calc(now->lft);
calc(now->rgt);
int res1=INT_MIN,res2=INT_MIN;
if(now->lft!=nullptr)
{
res1=now->lft->addval;
}
if(now->rgt!=nullptr)
{
res2=now->rgt->addval;
}
now->addval=max(res1,res2);
}
int query(int xorval)
{
node *now=root;
int ret=0;
for(int pos=31;pos>=0;--pos)//这里也要倒序
{
if(xorval&(1<<pos))
{
if(m&(1<<pos))
{
if(now->lft!=nullptr)
{
// assert(now->rgt!=nullptr);
ret=max(ret,(now->rgt!=nullptr?now->rgt->addval:0));
now=now->lft;
}
else
{
// assert(now->rgt!=nullptr);
return max(ret,now->rgt->addval);
}
}
else
{
if(now->rgt!=nullptr)
{
now=now->rgt;
}
else
{
return ret;
}
}
}
else
{
if(m&(1<<pos))
{
if(now->rgt!=nullptr)
{
// assert(now->lft!=nullptr);
ret=max(ret,(now->lft!=nullptr?now->lft->addval:0));
now=now->rgt;
}
else
{
// assert(now->lft!=nullptr);
return max(ret,now->lft->addval);
}
}
else
{
if(now->lft!=nullptr)
{
now=now->lft;
}
else
{
return ret;
}
}
}
}
return ret;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cin>>n>>m;
for(int i=0;i<n;++i)
{
cin>>A[i]>>B[i];
}
root=new node;
ptr.emplace_back(root);
for(int mask=1;mask<(1<<(n>>1));++mask)
{
int xorval=0,addval=0;
for(int i=0;i<(n>>1);++i)
{
if(mask&(1<<i))
{
xorval^=A[i];
addval+=B[i];
}
}
insert(xorval,addval,31,root);
}
calc(root);
int remain=n-(n>>1),ans=INT_MIN;
for(int mask=1;mask<(1<<remain);++mask)
{
int xorval=0,addval=0;
for(int i=0;i<remain;++i)
{
if(mask&(1<<i))
{
xorval^=A[(n>>1)+i];
addval+=B[(n>>1)+i];
}
}
ans=max(ans,query(xorval)+addval);
}
cout<<ans<<endl;
#ifdef LOCAL
for(node* &p:ptr)
{
delete p;
p=nullptr;
}
#endif
return 0;
}
Tips
- 通过程序中的
assert()不难看出须要特判空指针的情况以免RE,如果是使用数组实现的Trie就不用考虑这个问题。 - 仅在本地运行时(即
#ifdef LOCAL)才释放内存是因为本地不释放会内存泄漏,而OJ上如果循环释放内存会TLE,毕竟节点数最坏情况下是指数级的。

浙公网安备 33010602011771号