Description
IT = Inverse Transform
两个长度为 (2n) 的序列 (a,b). ( 下标从 (0) 到 (2n−1) )
满足$ 0≤a_i≤10^9$ , 且 由 (a) 变换而来, 变换如下:
(b_i=∑limits_{0≤j<2^n}f((i∨j)⊕i) a_j)
其中 (V) 表示按位或, (⊕) 表示按位异或. f(x)=[x的二进制中1的个数为偶数]f(x)=[x的二进制中1的个数为偶数]
(其中 [expression] 当 (expression) 为真时为 1, 否则为 0)
现在, (a) 序列被玩丢了...
请你通过 (b) 序列还原出 (a) 序列
Input
第一行一个数 (T) 表示测试数据组数
接下来对于每组数据 :
第一行一个整数 (n)
第二行 (2n) 个整数, 表示序列 (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≤bi≤10^9) )
Solution
不得不说是道很好的题啊.
题目中的(f((i∨j)⊕i)),当且仅当(i=0)且(j=1)时为1,其余情况皆为0.
正解用的是分治的方法,再向上合并,有点类似各大快速变换的思想.
首先考虑若知(a),如何(O(n log n))求(b).
由于总长度是(2^n),所以给分治带来了极大的便利。每次对([l,r])分治为([l,mid])与((mid,r])来解决。自底向上层层计算,对于分治区间([l,r]), 当下(b_i)的值表示:
那么分治完成后,(b_i)的值才是最终答案。
考虑最底层的情况([l,l]),此时(b_l=f((l∨l)⊕l)*a_l=a_l).
假设现在正在在分治([l,r]),记分治长度(len=(r-l+1)),已经计算出([l,mid])和((mid,r]) 的(b)值,现在考虑如何将(b)合并。
分治区间的长度是2的整数次幂,这给了一个很棒的性质,记(k=log_2len),若只看下标的后(k)位,我们会发现,对于左右区间,左边的最高位全是0,右边的最高位全是1。如果忽略最高位,对于(forall iin[l,mid])都有(i==i+(r-l+1)/2). 而随着分治的逐渐上推,(k)是不断加一加一的。
这意味着如果我们要求新的(b'_i),我们只需要考虑当前(i)的最高位(第(k)位)带来的影响。
记(g_k(i,j))表示只考虑(i)和(j)的后(k)位时,(f((i∨j)⊕i))的值。
首先看(iin(mid,r]), 它们的第(k)位都是1. 由于((1,0))和((1,1))计算出来都是(0),所以我们可以直接令(b'_i=b_{i-len/2}+b_i)
为什么?因为(b_{i-len/2})对应着满足(g_{k-1}(i-len/2,j)=1)的(a_j)的总和,而这些(j)同样满足(g_k(i,j)),所以这些(a_j)可以加进来。
(b_i)对应着满足(g_{k-1}(i,j)=1)的(a_j)的总和,而这些(j)同样满足(g_k(i,j)),所以这些(a_j)也可以加进来。
接着再看(iin[l,mid]),它们的第(k)位都是(0). 我们令(b'_i=b_i+(sum_{j=mid+1}^ra_j) -b_{i+len/2})。(b_i)的部分同上很好理解,因为((0,0))计算出来是(0),满足(g_{k-1}(i,j)=0)的(j)同样满足(g_k{i,j}),这些(a_j)可以加进来;另一部分比较特殊,因为((0,1))计算出来是1,满足(g_{k-1}(i+len/2,j)=1)的(j)必定不满足(g_k(i,j)=1),因为计算出来的1会使1的总数变为奇数,那么我们现在反而要变向使用“不合法的情况”,即满足(g_{k-1}(i+len/2,j)=0)的情况,容易表达为((sum_{j=mid+1}^ra_j) -b_{i+len/2})。
总结一下,我们有:
就可以自底向上(O(nlogn))上推得到(b)了。
下面看如何知(a)反推(b),把上述两个式子一加再化简就可以得到:
观察得到(b'_r=sum_{j=l}^r a_j),且(b'_{mid}=sum_{j=l}^{mid}a_j),所以(sum_{j=mid+1}^r a_j=b'_r-b'_{mid})
然后就神奇的反推下去了。代码奇短无比又简单。
#include <cstdio>
using namespace std;
const int N=1100000;
typedef long long ll;
namespace IO{/*{{{*/
const int SIZE=50000000;
char buffer[SIZE];
int pos;
void init(){fread(buffer,1,SIZE,stdin);}
char getch(){return buffer[pos++];}
ll getInt(){
char c=getch(); ll x=0,f=1;
while(c<'0'||c>'9'){if(c=='-')f=-f;c=getch();}
while('0'<=c&&c<='9'){x=x*10+c-'0';c=getch();}
return x*f;
}
}/*}}}*/
int bn,n;
ll a[N];
void solve(){
for(int i=n;i>1;i>>=1){
for(int j=0;j<n;j+=i){
ll s=a[j+i-1]-a[j+i/2-1];
for(int k=0;k<i/2;k++){
a[j+k]=(a[j+k]+a[j+i/2+k]-s)/2;
a[j+i/2+k]-=a[j+k];
}
}
}
}
void Main(){
bn=IO::getInt();
n=1<<bn;
for(int i=0;i<n;i++) a[i]=IO::getInt();
solve();
for(int i=0;i<n;i++) printf("%lld ",a[i]);
puts("");
}
int main(){
freopen("input.in","r",stdin);
IO::init();
int T=IO::getInt();
while(T--) Main();
return 0;
}