【UNR #7】比特迷宫
Description
小青鱼来到了重 (zhòng) 庆市的一个迷宫,名为比特迷宫。听说只有最聪明的人才能从里面走出。
这个迷宫看似容易,但在小青鱼即将走出迷宫的时候,却被 \(n=2^k\) 个比特机器人拦住了去路。这些机器人从左到右显示着 \(a_{0,}, a_1, \cdots, a_{n-1} ,\) 表示一个 \(n-1\) 次的多项式 \(P(x)=a_0+a_1 x+\cdots+a_{n-1} x^{n-1}\) 的系数。比特机器人喜欢比特,所以每个系数只可能是 0 或者 1 。
小青鱼是无法单独修改某一个比特机器人显示的系数的,但是这个迷宫提供了一个批量修改的操作:输入两个非负整数 \(a, b(a+b \leq n-1)\) ,显示第 \(i\) 次项系数的比特机器人会算出 \(x^a(1+x)^b\) 的第 \(i\) 次项系数 \(c_i\) ,然后将自己显示的值 \(a_i\) 修改为 \(\left(a_i+c_i\right) \bmod 2\) ,其中 \(\bmod 2\) 表示对 2 取模。
整体来看,这个操作的作用是将这个多项式 \(P(x)\) 在 \(\bmod 2\) 意义上加上 \(x^a(1+x)^b\) 。
由于这个迷宫是困难模式,小青鱼只能操作不超过 \(T\) 次。当多项式变为 0 的时候,也即所有比特机器人都显示 0 的时候,他就通关了。
小青鱼左思右想没有想到通关的方式,于是他找到了你来帮忙。
\(1\le k\le 20\),\(a_i\in\{0,1\}\),\(1.6\times 10^5\le T\le 10^6\)。
Solution
首先,\(\dbinom{n}{m}\mod 2=[n\&m==m]\)。也就是说,当 \(m\subset n\) 时,\(\dbinom{n}{m}\) 是奇数,反之为偶数。
现在我们需要步数最小,根据贪心的思路,每次操作都要清掉尽可能多的 1。
将 \(A\) 分成一段段区间(\(1\sim len,2\sim len+1,\cdots\)),每次处理其中一个,找到一个合适的 \(a,b\) 清掉尽可能多的 1。
设 \(A'_i\) 表示当 \(A_i=1\) 时,\(A'_i=1\);当 \(A_i=0\) 时,\(A'_i =-1\)。设 \(S_i=\sum_{j\subset i} A'_j\),表示在这个区间中,使用 \((1+x)^i\) 会造成多少贡献。1 变成 0 是正贡献,0 变成 1 是负贡献。每次更新 \(A_i\) 后都重新求一遍 \(S_i\),然后选择最大的 \(S_i\) 去更新 \(A_i\)。
\(S_i\) 的计算是经典的 FWT,直接套版子就好。
最后,这个算法的正确性并没有被严格的保证,存在被构造数据导致 WA 的情况,对此我们可以在一开始随机的进行几次操作,打乱原本的序列,减小被卡的可能性。
Code
#include<cstdio>
#include<stdlib.h>
#include<time.h>
#include<algorithm>
#define N 1100000
#define len 1024
using namespace std;
int n,T,mx,ans1,a[N],ans[N][2],b[2000];
void FWT()
{
for (int k=1;k<len;k<<=1)
for (int i=0;i<len;i+=(k<<1))
for (int j=0;j<k;++j)
b[i+j+k]+=b[i+j];
}
int main()
{
srand(time(NULL));
scanf("%d%d%d",&n,&T,&mx);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
for (int t=0;t<=n/500;++t)
{
++ans1;
ans[ans1][0]=t*500;
ans[ans1][1]=rand()%(n-ans[ans1][0]-1)+1;
for (int i=0;i<=ans[ans1][1];++i)
if ((i&ans[ans1][1])==i) a[ans[ans1][0]+i+1]^=1;
}
for (int i=1;i<=n-len+1;++i)
if (a[i])
{
for (int j=0;j<len;++j)
b[j]=a[i+j]?1:-1;
FWT();
int mx=0;
for (int j=1;j<len;++j)
if (b[j]>b[mx]) mx=j;
++ans1;
ans[ans1][0]=i-1;
ans[ans1][1]=mx;
for (int j=0;j<=mx;++j)
if ((mx|j)==mx) a[i+j]^=1;
}
for (int i=max(n-len,1);i<=n;++i)
{
if (a[i])
{
ans[++ans1][0]=i-1;
ans[ans1][1]=0;
}
}
printf("%d\n",ans1);
for (int i=1;i<=ans1;++i)
printf("%d %d\n",ans[i][0],ans[i][1]);
return 0;
}