zoukankan      html  css  js  c++  java
  • 【题解】HDU5921-5921 Binary Indexed Tree

    HDU5921-Binary Indexed Tree

    原题链接:https://vjudge.net/contest/403983#problem/K
    CCPC 2016 长春K

    赛场上想到了第一种思路的拆分情况,但是后期并没有深入想下去。

    赛后题解更多的都是用数位Dp。受到隔壁队伍和网上博客的启发,整理出了三种思路。

    虽然可能第一种是混合型的。

    并在此感谢xhk老板的指导

    一、数位Dp+二进制拆分


    题意:

    (sum^{n}_{l=1})(sum^{n}_{r=1})f(l,r)=(sum^{n}_{i=1})(sum^{n}_{j=1}{[g(l-1)+g(r)-2*g(lcp(l-1,r))]})


    g(i)表示i在二进制下1的个数


    将n二进制拆位,考虑从右往左第i位的贡献。


    记[i+1...lens]在十进制下为nxt[i+1],[1...i-1]在十进制下为pre[i-1]。


    1.计算gi第一步:左边的数不改变
    那么为了不大于原数,右边的数应取0...pre[i-1]。


    当ai==1时,有pre[i-1]+1的贡献


    2.计算gi第二步:左边的数可改变


    与上一种情况相对应,左边的数应取0...nxt[i+1]-1.
    对于从右往左数第i位1,显然此时右边的数可以任意取值,有2^(i-1)种取值。


    所以这种情况下,无论ai取值如何,都有nxt[i+1]*2^i的贡献。


    这个思路其实是二进制拆分下的数位Dp,只是把数位Dp的递归换了一种形式。

    代码实现

    #include<bits/stdc++.h>
    typedef long long ll;
    const ll MO = 1000000007;
    
    int T,bin[103],lens;
    ll n,ans,power[103],pre[103],nxt[103];
    
    ll read()
    {
        char ch = getchar();
        ll num = 0;
        bool fl = 0;
        for (; !isdigit(ch); ch=getchar())
            if (ch=='-') fl = 1;
        for (; isdigit(ch); ch=getchar())
            num = (num<<1)+(num<<3)+ch-48;
        if (fl) num = -num;
        return num;
    }
    int main()
    {
        T = read(), power[0] = 1;
        for (int i=1; i<=60; i++) power[i] = (power[i-1]<<1)%MO;
        while (T--)
        {
            n = read(), ans = lens = 0;
            for (ll x=n; x; x>>=1) bin[++lens] = x&1;
            pre[0] = nxt[lens+1] = 0;
            for (int i=1; i<=lens; i++) pre[i] = (pre[i-1]+power[i-1]*bin[i])%MO;
            for (int i=lens; i>=1; i--) nxt[i] = ((nxt[i+1]<<1)+bin[i])%MO;
            for (int i=1; i<=lens; i++)
            {
                if (bin[i]) pre[i-1]++, ans = (ans+pre[i-1])%MO;
                ans = (ans+nxt[i+1]*power[i-1]%MO)%MO;
            }
            ans = (n+1ll)%MO*ans%MO;
            for (int i=1; i<=lens; i++)
            {
                if (bin[i]) ans = (ans+MO-pre[i-1]*pre[i-1]%MO)%MO;
                ans = (ans-nxt[i+1]*power[i-1]%MO*power[i-1]%MO+MO)%MO;
            }
            printf("%lld
    ",ans);
        }
        return 0;
    }
    

    二、纯二进制拆分(zx)

    上一种是二进制拆分,但是核心思路还是用到了数位DP,下面的这种是纯二进制,来自zx

    题解:对于每一位i(i从1开始),pow2[i]一组,组数记为tuan,除组数以外的1的数量记为san

    以第三位为例,可以分成tuan=1,san=0;第四位tuan=0,san=1;

    为了把0算作一个数,所以n++,n表示从0开始的数字的数量

    tuan=n/pow2[i],san=(n%pow2[i]/pow2[i-1])*(n%pow2[i-1])

    遍历每一位的时候,ansleft+=n·(tuan·pow2[i-1]+san)//把该位是1的个数加上

    ansright+=tuan·pow2[i-1]·pow2[i-1]+san·san//因为对于每一组它们的lcp都包括该位

    pow2[i]: 32 16 8 4 2 1

    num=0 0 0 0 0 0 0

    num=1 0 0 0 0 0 1

    num=2 0 0 0 0 1 0

    num=3 0 0 0 0 1 1

    num=4 0 0 0 1 0 0

    num=5 0 0 0 1 0 1

    num=6 0 0 0 1 1 0

    num=7 0 0 0 1 1 1

    num=8 0 0 1 0 0 0

    #include<bits/stdc++.h>
    using namespace std;
    typedef unsigned long long ull;
    #define debug(x) cout<<#x<<':'<<x<<endl;
    const int mod=1e9+7;
    ull pow2[65];
    ull mul(ull a,ull b){
        a%=mod,b%=mod;
        return a*b%mod;
    }
    int main()
    {
        pow2[0]=1;
        for(int i=1;i<64;i++)pow2[i]=pow2[i-1]*2;
        int T,t=0;scanf("%d",&T);
        while(T--)
        {
            ull n;scanf("%llu",&n);n++;//n为算上零后的个数,
            ull ansleft=0,ansright=0;
            for(int i=63;i>=1;i--)
            {
                ull san=mul((n%pow2[i])/pow2[i-1],n%pow2[i-1]);
                ull tuan=(n/pow2[i])%mod;
                ansright=(ansright+mul(tuan,mul(pow2[i-1],pow2[i-1]))+mul(san,san))%mod;
                ansleft=(ansleft+mul(n,mul(tuan,pow2[i-1])+san))%mod;
            }
            printf("Case #%d: %lld
    ",++t,(ansleft+mod-ansright)%mod);
        }
    }
    

    三、数位DP

    思路:

    每个节点对答案的贡献为 子树个数*(n-子树个数+1),只和子树个数相关,计算出子树个数为2^k的树有多少个.

    注意:最大节点编号的限制那些编号特别大的子节点是不能算入答案的。所以我们需要把这种边界情况单独计算。

    代码实现
    #include <bits/stdc++.h>
    #define INF 2147483640
    #define eps 1e-9
    #define MAXN 30010
    #define MOD 1000000007
    typedef long long ll;  
    using namespace std;
    int t,pos,j,a[65];
    ll n,dp[65][2];
    ll dfs(int pos,int limit)
    {
        if(pos == j) return !limit;
        if(dp[pos][limit] >= 0) return dp[pos][limit];
        int up = limit ? a[pos] : 1;
        ll ans = 0;
        for(int i = 0;i <= up;i++) 
        {
            ans += dfs(pos-1,limit && i == up);
            if(ans >= MOD) ans %= MOD;
        } 
        dp[pos][limit] = ans;
        return ans;
    }
    void solve(ll x)
    {
        pos = 0;
        while(x)
        {
            a[pos++] = x & 1;
            x >>= 1;
        }
    }
    int main()
    {
        scanf("%d",&t);
        for(int T = 1;T <= t;T++)
        {
            scanf("%I64d",&n);
            solve(n);
            ll ans = 0;
            for(j = 0;(1ll<<j) <= n;j++)
            {
                memset(dp,-1,sizeof(dp));
                ans = (ans + ((dfs(pos-1,1)*((1ll<<j) % MOD) % MOD)*((n + 1 - (1ll<<j)) % MOD)) % MOD) % MOD;
                if(a[j])
    			{
    				ll temp = (n - ((n>>j)<<j) + 1) % MOD;
    				ans = (ans + (temp*((n + 1 - temp) % MOD)) % MOD) % MOD;
    			} 
            } 
            printf("Case #%d: %I64d
    ",T,ans);
        }
    }
    
    
  • 相关阅读:
    APUE.3源码编译(Ubuntu16.04)
    《UNIX环境高级编程》(第三版)阅读笔记---2018-5-9
    css回归之用户界面
    css回归之文本
    js回归之字符串
    js回归之BOM
    js回归之事件
    百度前端面试总结
    书单
    剑指offer做题收获之一:补码
  • 原文地址:https://www.cnblogs.com/Shayndel/p/13976636.html
Copyright © 2011-2022 走看看