题目描述
给出一个长度为 $2^n$ 的序列,编号从0开始。每次操作后,如果 $i$ 与 $j$ 的二进制表示只差一位则第 $i$ 个数会加上操作前的第 $j$ 个数。求 $t$ 次操作后序列中的每个数是多少。
输入
第一行两个正整数 n , t,意义如题。
第二行 2^n 个非负整数,第 i 个数表示编号为 i-1 的城市的初始货物存储量。
n<=20 t<=10^9
输出
输出一行 2^n 个非负整数。
第 i 个数表示过了 t 天后,编号为 i-1 的城市上的货物数量对 1e9+7 取模的结果。
样例输入
3 2
1 2 3 4 5 6 7 8
样例输出
58 62 66 70 74 78 82 86
题解
FWT+快速幂
显然构建 $b$ 数组,其中 $b[0]=1$ ,$b[2^i]=1$ ,其余为 $0$ ,那么原序列 $a$ 经过一次操作后得到的新序列就是 $aoplus b$ ,其中 $oplus$ 表示两个数组的异或卷积。
于是就好办了,先求出 $a[]$ 和 $b[]$ 的FWT,然后直接按位计算 $c[i]=a[i]*b[i]^t$ ,再求逆fwt即可。
时间复杂度 $O(2^n·n)$
注意本题卡常,因此必须加读入优化和输出优化。
#include <cstdio> #include <cctype> #define N 1050000 #define mod 1000000007 typedef long long ll; ll a[N] , b[N]; inline char nc() { static char buf[100000] , *p1 , *p2; return p1 == p2 && (p2 = (p1 = buf) + fread(buf , 1 , 100000 , stdin) , p1 == p2) ? EOF : *p1 ++ ; } inline int read() { int ret = 0; char ch = nc(); while(!isdigit(ch)) ch = nc(); while(isdigit(ch)) ret = ((ret + (ret << 2)) << 1) + (ch ^ '0') , ch = nc(); return ret; } char pbuf[15000000] , *pp = pbuf; inline void write(ll x) { static int sta[12]; int top = 0; if(!x) *pp ++ = '0'; while(x) sta[top ++ ] = x % 10 , x /= 10; while(top -- ) *pp ++ = sta[top] ^ '0'; *pp ++ = ' '; } ll pow(ll x , int y) { ll ans = 1; while(y) { if(y & 1) ans = ans * x % mod; x = x * x % mod , y >>= 1; } return ans; } void fwt(ll *a , int n , int flag) { int i , j , k , t; for(i = 1 ; i < n ; i <<= 1) for(j = 0 ; j < n ; j += (i << 1)) for(k = j ; k < j + i ; k ++ ) t = a[k] , a[k] = (t + a[k + i]) * flag % mod , a[k + i] = (t - a[k + i] + mod) * flag % mod; } int main() { int n = 1 << read() , m = read() , i; for(i = 0 ; i < n ; i ++ ) a[i] = read(); b[0] = 1; for(i = 1 ; i < n ; i <<= 1) b[i] = 1; fwt(a , n , 1) , fwt(b , n , 1); for(i = 0 ; i < n ; i ++ ) a[i] = a[i] * pow(b[i] , m) % mod; fwt(a , n , 500000004); for(i = 0 ; i < n ; i ++ ) write(a[i]); fwrite(pbuf , 1 , pp - pbuf , stdout); return 0; }