zoukankan      html  css  js  c++  java
  • 奇怪的姿势?高维前缀和

    奇怪的姿势:高维前缀和

    首先请先看一题:

    Bzoj5092:

    一句话题面:
    我们做xor前缀和后,对每一个i,输出j<i,max(s[i]^s[j]+s[j])。

    一般人估计上来就想可持久化Trie了。
    然而我们发现,对于某一位,如果s[i]这位为1,对于s[j],这位如果是0的话贡献为1,是1的话贡献为0。
    but,如果s[i]这位为0,那么对于s[j],这位如果是0的话贡献为0,是1的话贡献为2。
    等等,这怎么还带进位的?这玩意能做?

    可持久化Trie当然是做不了的,于是我们需要一种叫做高维前缀和的东西。
    考虑我们如果把i的二进制表示看做一个集合(状态压缩总会吧),我们要统计i的超集的一些信息。
    说人话,就是对于i,我们要满足统计(j&i)==i的j的一些信息。
    这就是高维前缀和所做的事情了。
    如何统计?
    我们先从低到高满足j和i存在差异的第一个位置,然后对不包含这个位置的i补全这个位置,并统计答案。
    也就是:
    if( ! ( i & ( 1 << b ) ) ) f[i] = f[i] add f[i|(1<<b)]
    这里的add可以是任何满足加和性质的运算,比如+,^,min(),max(),等等。
    考虑这样做为什么是对的,首先和i在更低位置有差异的已经被统计了(废话,看你前面位数怎么循环的)。
    然后考虑此时的f[j](这里j=i|(1<<b)),他包含了和j在更低位置存在差异的数值的信息。
    也就是说,我们在把f[j]的信息加入f[i]的同时,同时加入了和i在这一位有差异且在更低位有差异的所有值的信息。
    所以这个东西不会漏算。
    为什么不会算重?因为我们统计的时候每次的最高差异位是不同的!

    考虑如何用这个东西来完成Bzoj5092。
    我们令f[k]表示二进制表示包含k的最靠前位置。
    然后我们从高到低贪心枚举位数。
    如果s[i]的当前位为1,我们对于s[j]此位选择0和1都相同,我们可以忽略这位。
    如果s[i]当前位为0,我们就要贪心选择s[j]当前位为1的j。
    假设我们已经选出的状态为cur。如果我们能够找到这样的j的话,需要满足的条件是:f[cur^(1<<b)]<=i。
    去判定一下就可以了。

    代码:

     1 #include<cstdio>
     2 #include<cstring>
     3 #include<algorithm>
     4 const int maxn=3e5+1e2,maxm=4e6+1e2,lim=2097152;
     5 
     6 int in[maxn],f[maxm];
     7 
     8 inline void pre() {
     9     for(int b=0;b<=20;b++)
    10         for(int i=0;i<lim;i++)
    11             if( ! ( i & ( 1 << b ) ) ) f[i] = std::min( f[i] , f[i|(1<<b)] );
    12 }
    13 
    14 int main() {
    15     static int n;
    16     scanf("%d",&n) , memset(f,0x3f,sizeof(f));
    17     for(int i=1;i<=n;i++) {
    18         scanf("%d",in+i) , 
    19         in[i] ^= in[i-1] , 
    20         f[in[i]] = std::min( f[in[i]] , i );
    21     }
    22     pre();
    23     for(int i=1,cur;i<=n;i++) {
    24         cur = 0;
    25         for(int b=20;~b;b--) if( ! ( in[i] & ( 1 << b ) ) ) {
    26             if( f[cur|(1<<b)] <= i ) cur |= ( 1 << b );
    27         }
    28         printf("%d
    ",(in[i]^cur)+cur);
    29     }
    30     return 0;
    31 }
    View Code



    其实关于高维前缀和还有一道更经典的题目:
    SPOJ TLE:

    网上抄的中文题面:
    给出n个数字c,求非负整数序列a,满足a<2^m。
    并且有a[i]&a[i+1]=0,对于每个a[i],要保证a[i]不是c[i]的倍数。
    求这样的a[i]序列的个数。

    我们考虑DP,设f[i][j]表示前i位最后一位为j的方案数。
    我们枚举下一位选择的数,假设为x,这样我们需要满足a[i+1]%c[i+1]!=0。
    且对于第i位选择的数,j & x == 0。
    这是什么意思呢?考虑我们对x的二进制表示求补集为w, j需要为w的子集。
    高维前缀和也能统计子集信息,只要我们修改一下转移条件和转移来源就好了。
    if( i & ( 1 << b ) ) f[i] += f[i^(1<<b)]。
    就是把这一个差异位去掉啦。
    什么,你说枚举子集,我们也能够用一个for循环实现?
    for(int ss=s;ss;ss=s&(ss-1))
    但是这不能原地址运算吧,而高维前缀和可以(可能在统计子集上也就这点优势了)。
    然而统计超集,你告诉我一个for循环怎么做?这才是高维前缀和发挥作用的地方。

    代码:

     1 #include<cstdio>
     2 #include<cstring>
     3 const int maxn=55,maxe=32873;
     4 const int mod=1000000000;
     5 
     6 int f[maxn][maxe],sum[maxe],ans;
     7 int c[maxn];
     8 int n,m,lim,neg;
     9 
    10 inline void rebuild(int* sou) {
    11     memcpy(sum,sou,sizeof(sum));
    12     for(int i=0;i<m;i++)
    13         for(int j=0;j<lim;j++)
    14             if( ( j & ( 1 << i ) ) )
    15                 sum[j] = ( sum[j] + sum[j^(1<<i)] ) % mod;
    16 }
    17 inline void trans(int* f,int c) {
    18     for(int i=0;i<lim;i++)
    19         if( i % c )
    20             f[i] = ( f[i] + sum[neg^i] ) % mod;
    21 }
    22 
    23 int main() {
    24     static int T;
    25     scanf("%d",&T);
    26     while(T--) {
    27         memset(f,0,sizeof(f)) , ans = 0;
    28         scanf("%d%d",&n,&m) , lim = 1 << m , neg = lim - 1;
    29         for(int i=1;i<=n;i++) scanf("%d",c+i);
    30         for(int i=0;i<lim;i++) if( i % c[1] ) f[1][i] = 1;
    31         for(int i=2;i<=n;i++) rebuild(f[i-1]) , trans(f[i],c[i]);
    32         for(int i=0;i<lim;i++) ans = ( ans + f[n][i] ) % mod;
    33         printf("%d
    ",ans);
    34     }
    35     return 0;
    36 }
    View Code



    PS:话说作为一个高二省选选手,以前竟然连这东西都不会,我果然还是太菜了啊。
    我不够努力;我不够强;我还能做得更好,然而我没有;我是错误的;我在犯罪;我该死。

  • 相关阅读:
    ExtJS小技巧
    Oracle 表的行数、表占用空间大小,列的非空行数、列占用空间大小 查询
    NPM 私服
    IDEA 不编译java以外的文件
    SQL 引号中的问号在PrepareStatement 中不被看作是占位符
    Chrome 浏览器自动填表呈现淡黄色解决
    批量删除Maven 仓库未下载成功.lastupdate 的文件
    Oracle 11g 监听很慢,由于监听日志文件太大引起的问题(Windows 下)
    Hibernate 自动更新表出错 建表或添加列,提示标识符无效
    Hibernate 自动更新表出错 More than one table found in namespace
  • 原文地址:https://www.cnblogs.com/Cmd2001/p/8519387.html
Copyright © 2011-2022 走看看