HDU 4321 Arcane Numbers 2 按位处理, 想法计数题

28 篇文章 0 订阅

题目大意:

就是对于给出的A, B, N, A <= 1e4, B <= 1e16, N <= 1e12, 统计在B + A, B + 2*A, B + 3*A, B + 4*A.... B + N*A这N个数的二进制表示法下一共有多少个1


大致思路:

这个题调了好久...想法虽然可行但是写错了几个细节的位置...于是调了半个小时才调过....感觉思路明确了复杂度也就清楚了

思路写在代码注释里了


代码如下:

Result  :  Accepted     Memory  :  1620 KB     Time  :  140 ms

/*
 * Author: Gatevin
 * Created Time:  2015/8/11 23:31:36
 * File Name: Sakura_Chiyo.cpp
 */
#include<iostream>
#include<sstream>
#include<fstream>
#include<vector>
#include<list>
#include<deque>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<bitset>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<cmath>
#include<ctime>
#include<iomanip>
using namespace std;
const double eps(1e-8);
typedef long long lint;

/*
 * 首先要求的是B + A, B + 2*A, .... B + N*A这N个数的二进制中总共有多少个1
 * 那么我们按位一步一步来, 先统计N个数中第K位是1的个数
 * 第K位按照二进制从右向左数, 从1开始数
 * 那么不难发现对于第K位B + T*A 和 B + T*A + (1 << K)*A一定是一样的
 * 那么我们在计算第K位的时候对于 1 <= T <= (1 << K) - 1计算即可
 * 对于B + i*A, 1 <= i <= (1 << K) - 1相同的贡献个数为 (N - i)/(1 << K) + 1个这里是整数除法
 * 但是由于K可以比较大, 所以对于第K位不能暴力枚举所有可能的T
 * 那么当(1 << K) <= A的时候我们暴力枚举
 * 当(1 << K) > A时, 我们考虑这样一个事实:
 * 不难发现如果当前第i项 B + i*A对于(1 << K)取模之后剩下的部分, 在接下来添加A的过程中会有连续的一段不向第K位二进制进位
 * 那么就会出现连续的B + i*A, B + (i + 1)*A,...., B + j*A一整段的第K位都是0, 然后又连续一段是1, 两者交替的情况
 * 于是这样我们只需要计算连续的一段多长就可以了, 由于(1 << K) > A, 前面一段对(1 << K)取模剩余res的时候接下来一段长度是((1 << K) - res - 1)/A
 * 那么枚举第K位的时间复杂度是O(log(B + A*N))而每一次枚举, 需要计算区间[1, (1<<K) - 1], 一段区间计算次数是大约是(1<<K)/(((1 << K) - res - 1)/A)次
 * 而(1 << K) / (((1 << K) - res - 1)/A)也就是是O(A)的级别
 * 于是整体复杂度也就是O(log(B + A*N)*A)
 */

int T;
lint A, B, N;

lint solve()
{
    lint ans = 0;
    lint mx = B + A*N;
    for(int K = 1; (1LL << (K - 1)) <= mx; K++)//从右向左第K位
    {
        lint L = 1LL << (K - 1);
        lint S = 1LL << K;
        lint rest = (B + A) % L;
        int now = (B + A) & L ? 1 : 0;//当前第K位是0还是1
        lint T = 1;
        while(T <= S && T <= N)
        {
            lint step = (L - rest - 1) / A;//接下来会有的连续的now(0 or 1)的个数
            step = min(step, min(N - T, S - T));
            
            //也就是说[T, T + step]都是now
            //一共step + 1个, 对答案的贡献是sigma((N - i) / (1 << K)) T <= i <= T + step
            //cout<<L<<" "<<rest<<" "<<now<<" "<<T<<" "<<step<<" "<<K<<" "<<ans<<endl;
            //getchar();
            if((N - T) / S + 1 == (N - (T + step))/S + 1) ans += (step + 1)*((N - T)/S + 1) * now;
            else
            {
                lint mid = (N - T) / S * S;//[mid, N - T]这一段都是相同的, [N - (T + step), mid - 1]是相同的 (相同指的是B + (T + (1 << K))*A的个数相同)
                ans += ((N - T) / S + 1) * (N - T - mid + 1) * now;
                ans += ((N - (T + step)) / S + 1) * (mid + T + step - N) * now;
            }
            rest = (rest + (step + 1)*A) % L;
            T += step + 1;
            now = (B + T*A) & L ? 1 : 0;//接下来是相反的连续段
        }
    }
    return ans;
}


int main()
{
    scanf("%d", &T);
    for(int cas = 1; cas <= T; cas++)
    {
        scanf("%I64d %I64d %I64d", &A, &B, &N);
        printf("Case #%d: %I64d\n", cas, solve());
    }
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值