[数位DP] ZROI 2017 提高3 T1 树状数组

来源:互联网 发布:矩阵怎么计算 编辑:程序博客网 时间:2024/05/22 03:13

不错的题。这个应该从二进制角度考虑。我们知道树状数组每次就是把二进制最末尾的 1 去掉。

那么考虑对 rl1 分别加 11 后,有值的节点个数即 cnt(r)+cnt(l1)2cnt(prefix(r,l1)) 。其中cnt 是二进制下 1 的个数,prefix 是公共前缀。这个不难得到。

然后就是如何计算总贡献了:

0x<yncnt(x)+cnt(y)2cnt(prefix(x,y))

这可以简单的数位 DP 得到:f[i][0/1][0/1] 表示填了前 i 位,x 是否已小于yy 是否已小于 n ,的贡献和。还要记个 g[i][0/1][0/1] 表示方案数。就可以转移了。

#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MOD=1e9+7;typedef long long LL;int _test,f[70][2][2],g[70][2][2],a[70]; //f[i][0:L==R 1:L<R ][0:R==n 1:R<n ]LL n;int Solve(){    memset(f,0,sizeof(f)); memset(g,0,sizeof(g)); a[0]=0;    LL _n=n; do a[++a[0]]=_n&1, _n>>=1; while(_n);     g[a[0]+1][0][0]=1;    for(int i=a[0]+1;i>=2;i--)     for(int j1=0;j1<=1;j1++)      for(int j2=0;j2<=1;j2++) if(g[i][j1][j2]){       for(int r=0;r<=(j2?1:a[i-1]);r++)        for(int l=0;l<=(j1?1:r);l++){            (f[i-1][j1|(l<r)][j2|(r<a[i-1])]+=(f[i][j1][j2]+(LL)g[i][j1][j2]*(l+r-(l&&r&&!j1)*2))%MOD)%=MOD;            (g[i-1][j1|(l<r)][j2|(r<a[i-1])]+=g[i][j1][j2])%=MOD;                   }      }    return (f[1][1][0]+f[1][1][1])%MOD;}int main(){    scanf("%d",&_test);    while(_test--){        scanf("%lld",&n);        printf("%d\n",Solve());    }    return 0;}