【题解】4.13模拟赛,学习(51nod)
\(Description:\)
给出长为n的序列,有n个元素\(a_i\),求出对于每个i的\(\sum_{i\&j=j}a_j\)
\(Sample\) \(Input:\)
8
1
2
4
8
16
32
64
128
\(Sample\) \(Output:\)
1
3
5
15
17
51
85
255
\(Solution:\)
这题一眼还是容斥啊,真糟糕。。。
突然发现题面告诉我容斥是个**
题面提示里面告诉我们要求前缀和,要一维一维求,不然容斥项数太多对复杂度影响大。
那么发现了前缀和这个词,考虑一下这个鬼东西。
欧,好像有点道理,考虑给出的转移条件 \(i\&j=j\)
这不就告诉我们 \(j\) 是 \(i\) 的二进制子集么?
那么只要当前的 \(i\) 枚举出他的子集就可以了,枚举子集时发现这个枚举其实和前缀和思想很像
就是把一位的 \(1\) 消掉,那么考虑一种睿智的做法:
弄一个二十一维的前缀和数组。。。
当然也可以状压!
不过若想写400行,我也拦不住嘛,对不对?
只能说:
可以呀,孙贼,你挺会玩儿啊!
开个玩笑,开个玩笑,别打我呀~~,别别别——————————————————啊!
其实这算法啊还有一个名字——
FMT
这里就不讲了,我先去搬搬LG的博客。
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=(1<<21),M=1000;
#define dd s=getchar()
inline int rd(){
int f=1,x=0;char dd;
while(!isdigit(s)) { if(s=='-')f=-1;dd;}
while(isdigit(s)) { x=(x<<1)+(x<<3)+(s^48);dd;}
return x*f;
}
#undef dd
#define dd putchar
inline void wrt(int x){
if(x<0) dd('-'),x=-x;
if(x>9) wrt(x/10);
dd(x%10^48);
}
int a[N+5],ans[N+5];
inline void FMT(int *f){
for(int j=0;j<=21;++j) for(int i=0;i<n;++i){
if(i&(1<<j)) f[i]+=f[i^(1<<j)];
}
}
int main(){
n=rd();
for(int i=0;i<n;++i) a[i]=rd();
FMT(a);
for(int i=0;i<n;++i) wrt(a[i]),putchar('\n');
; return 0;
}