Time Limit: 3000 ms Memory Limit: 256 MB
Description
IT = Inverse Transform
两个长度为 (2^n) 的序列 (a,b). ( 下标从 0 到 (2n−1) )
满足 (0≤a_i≤10^9) , 且 (b) 由 (a) 变换而来, 变换如下:
其中 (∨) 表示按位或, (⊕) 表示按位异或. (的二进制中的个数为偶数f(x)=[x的二进制中1的个数为偶数])
(其中 [expression] 当 expression为真时为 1, 否则为 0)
现在, (a) 序列被玩丢了...
请你通过 (b) 序列还原出 (a) 序列
Input
第一行一个数 (T) 表示测试数据组数
接下来对于每组数据 :
第一行一个整数 (n)
第二行 (2^n) 个整数, 表示序列 (b)
Output
输出包括 (T)行
每行 (n) 个整数, 表示序列 (a)
Sample Input
4
0
1
1
1 3
2
5 3 4 10
3
101 91 92 104 93 105 108 190
Sample Output
1
1 2
1 2 3 4
31 24 26 23 27 23 24 12
HINT
对于10% 的数据 (n≤5)
对于另外10% 的数据(n≤10)
对于100% 的数据 : (T≤5,1≤n≤20)
数据保证 (b) 序列 能 唯一地对应一个 (a) 序列
且保证对应的 (a) 序列满足 (0≤a_i≤10^9)
( 注意 : 但 不保证 读入的 (b) 序列满足 (0≤b_i≤10^9) )
Solution
- 10%
这个直接大力高斯消元就好了,注意是。。不保证(b)是int所以要用long long。。
(另外10%不会qwq)
- 100%
首先观察一下那个诡异的变换((i∨j)⊕i)(记作(change(x,y))好了)我们考虑一下把不同的(0)和(1)组合带进去会得到什么:
会发现带进去的除了((0,1))以外其他全部都是(0)
然后我们再看一下那个诡异的(f)函数,会发现我们需要考虑的只是(change)后的(1)的个数的奇偶性
然后因为这个东西每位的运算是独立的,再加上是二进制,可以考虑分治来解决这个问题
(b)推(a)比较烦,那么就先看(a)推(b)
考虑每次将这个数列分成两半,对于([l,mid])中的(b_i),我们只求(b_i=sumlimits_{j=l}^{mid}f(change(i,j))a[j])
同理([mid+1,r])中的(b_i=sumlimits_{j=mid+1}^{r}f(change(i,j))a[j])
换句话来说,就是对于分成的每一个区间,我们只求这个区间内对答案的贡献,按照这样的方式,分到最后区间长度为(1)时,我们可以得到(b_i=a_i),然后再将这些分开的区间一层一层地合并,同时统计跨区间的贡献,最后得到完整的(b)数组(有点像。。线段树的pushup一样?)
那么现在考虑怎么统计跨区间的贡献
我们考虑将([l,mid])和([mid+1,r])合并成([l,r])的情况,考虑这样划分出来的区间的性质,必定是([2^n,2^m-1])这样的形式,记([l,mid])的区间长度为(2^k),([l,mid])和([mid+1,r])还满足这两个区间对应的下标写成二进制后,后(k)位是一样的,只有第(k+1)位不同,举([0,1])和([2,3])为例子,两个区间对应的下标写成二进制后分别是(00,01)和(10,11),对应起来只有第(2)位是不同的,而且左边的一组这位全是(0),右边的一组这位全是(1)
清楚了这样的性质之后,再考虑合并区间就方便多了
为了方便我们将([l,mid])记作(bx)(下标从(l)到(mid) ),([mid+1,r])记作(by)(下标从(mid+1)到(r) ),这两个区间合并后在大区间记作(b)(下标从(l)到(r) ),(b)由(b'x)和(b'y)两个部分组成,其中(b'x)的下标从(l)到(mid),(b'y)的下标从(mid+1)到(r)
首先考虑(b'y[i])是怎么得到的,首先肯定是(by[i])加上(sumlimits_{j=l}^{mid}f(change(i,j))a[j])得到的,而由于(bx)和(by)的下标满足上面的性质,所以我们只用考虑不同的那一位对(1)的个数的奇偶性的影响
而因为我们的(change)只有带入((0,1))后得到的是(1),也就是说只有两个数不同的那一位分别为(0)和(1)的时候才会对奇偶性有影响,所以我们可以发现,当(i in [mid+1,r])时,(sumlimits_{j=l}^{mid}f(change(i,j))a[j]=bx[i-mid])(因为这样不同的两位带进去是((1,0)),得出来是(0),不会对奇偶性造成影响,而除去不同的那一位,(i)和(i-mid)的其他位是一样的,贡献相同),所以我们可以知道:
同理,考虑(b'x[i])是怎么得到的,肯定也是(bx[i])加上(sumlimits_{j=mid+1}^{r}f(change(i,j))a[j])得到的,从同样的角度来分析,会发现(b'x[i])在计算的时候,我们带进去考虑的不同的那位是((0,1)),得出来是(1),会影响奇偶性,所以那一大段sigma应该是等于计算(by[i+ mid])中(f)值为(0)的(a[j])的和,我们记(sumlimits_{i=mid+1}^{r}a[i]=sum),那么:
于是乎顺推的问题就很愉快滴解决了(感觉有点像FFT?)
但是现在的问题是知道(b)来求(a)啊,也就是我们要从(b'x)和(b'y)推回(bx)和(by) ,所以我们把上面的两条式子稍微变一下:
看起来不错,但是那个(sum)怎么办?
我们再考虑一下(by[r])(也就是(by)部分的最后一位)的值是哪些(a)贡献过来的,因为(r)是(2^k-1)的形式,所以写成二进制之后(不看前导零)都是(1),也就是从下面的更小的区间合并上来时,怎么算(1)的个数的奇偶性都不会有变化,也就是说([l,r])这个区间内的所有(a)都是对其有贡献的,即(by[r]=sumlimits_{i=l}^{r}a[i]),而(bx[mid])(也就是(bx)部分的最后一位)则是除了从右边数第(k)位是(0)外,其他位和(r)相同,稍微思考一下其推上来的过程可以发现(bx[mid]=sumlimits_{i=l}^{mid}a[i])
那么我们的(sum=by[r]-bx[mid])就好了
然后就一路推下去长度从(n)推到(1)就可以将(a)完整还原出来啦
代码大概长这个样子,十分简短
(题外话:听说这个其实跟FWT很像?然后写起来的循环跟FFT长得一样样的除了最外层逆过来枚举了)
#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
const int MAXN=(1<<20)+10;
ll a[MAXN];
int n,m,T;
int main(){
#ifndef ONLINE_JUDGE
freopen("a.in","r",stdin);
#endif
scanf("%d",&T);
ll sum;
for (int o=1;o<=T;++o){
scanf("%d",&n);
n=1<<n;
for (int i=0;i<n;++i) scanf("%lld",a+i);
for (int len=n;len>=1;len>>=1){
for (int st=0;st<n;st+=len){
sum=a[st+len-1]-a[st+(len>>1)-1];
for (int i=0;i<(len>>1);++i){
a[st+i]=(a[st+i]+a[st+i+(len>>1)]-sum)/2;
a[st+i+(len>>1)]=a[st+i+(len>>1)]-a[st+i];
}
}
}
for (int i=0;i<n;++i)
printf("%lld ",a[i]);
printf("
");
}
}