第一次(在牛客的数据结构课)系统地听完了一节讲前缀和的课,发现自己以前学了个寂寞
(正式)初次见面,请多关照!
知识点:
1.前缀和 and 差分数组
s0=a0,si=si-1+ai
满足这样关系的s称为数组a的前缀和数组
d0=a0,di=ai-1-ai
满足这样关系的d称为数组a的差分数组
前缀和和差分运算互为逆运算
因为对a做一次差分,再对差分数组做一次前缀和运算得到原数组
2.广义前缀和 and 应用
只要每一次操作产生的影响覆盖之前的结果,不覆盖之后的;且有反向操作可以与这一次操作的效果抵消,就可视为是广义的前缀和
应用:矩阵链乘、卷积(待学习)
3.高阶前缀和
·求k阶前缀和 系数满足 C(k,k) C(k,k+1)...C(k,k+i-1)...C(k,k+n-1)
·可以用来静态维护多项式:n次多项式做n+1次差分后得到常数。据此先用差分数组标记,可以通过求n+1次前缀和得到ans
4.高维前缀和
考虑问题:
定义一个集合的价值为:该集合所拥有的物品权值之异或和。
现在智乃酱想问你m个问题,对于每个问题他都会给你一个k个物品集合U,他想让你告诉她U的所有子集的价值之和,U的所有超集的价值之和。在本问题中,全集的定义为这N个物品组成的集合。(1<=N<=20,1<=M<=1e5)
链接:https://ac.nowcoder.com/acm/contest/19483/B
分析
1.由于m较大,n较小,每次询问中,如果以O(1)的时间复杂度答案才能很快算出。
2.每次询问对之后的询问没有影响。
考虑预处理(前缀和)。
注意维数较高时不写成dp[][][][][]...的形式,而使用状态压缩
细节都在注释里
1 #include<bits/stdc++.h> // O(n 2^n) 2 #define ll long long 3 using namespace std; 4 const int N = 5000004; 5 int n,m,k,x; 6 int a[N]; 7 ll pre[N],suf[N]; 8 int maxx; 9 10 void init(){ 11 maxx=(1<<n); 12 for(int i=0;i<maxx;i++){ //状态压缩 :表示所有物品取或者不取的一个总状态 13 int tem=0; 14 for(int j=0;j<n;j++){ //枚举第j件物品 15 if(i&(1<<j)) tem=(tem^a[j]); //当前状态是否取了物品j 16 } 17 pre[i]=tem; 18 suf[i]=tem; 19 } 20 for(int j=0;j<n;j++){ 21 for(int i=0;i<maxx;i++){ 22 if(i&(1<<j)) pre[i]+=pre[i^(1<<j)]; //prefix 子集和 还可能含有U中没有的元素 23 else suf[i]+=suf[i^(1<<j)]; //suffix 超集和 还可能含有U中有的元素 24 } 25 } 26 } 27 28 int main(){ 29 cin>>n>>m; 30 for(int i=0;i<n;i++) cin>>a[i]; 31 init(); 32 while(m--){ 33 cin>>k; 34 if(k==0){ 35 cout<<pre[0]<<" "<<suf[0]<<endl; 36 continue; 37 } 38 int tem=0; 39 for(int i=1,x;i<=k;i++){ 40 cin>>x; 41 tem=(tem|(1<<(x-1))); 42 } 43 cout<<pre[tem]<<" "<<suf[tem]<<endl; 44 } 45 return 0; 46 }