Hihocoder 1259 A Math Problem(数位DP+公式推导)

2015北京区域赛 K.A Math Problem

题目传送门
提及传送门

UVA提交会RE,no why!

题意

定义一个集合,集合的函数映射为:

f(n)=f1=1,3fn×f2n+1=f2n×(1+3fn),n=1 (1)n>1 (2)

定义 [1,n] 中的任意数字为 si ,然后规定 g(x) [1,n] 中令 f(si)%k==x si 的个数.

g(x) 异或的结果

解题思路

(2) 简化得到 3fn×(f2n+1f2n)=f2n ,由于 f1=1 ,所以 f2n+1f2n>0 .
然后将 fan<6fn 带入 (2) 中可以得到 3fn×(f2n+1f2n)<6fn 得到 f2n+1f2n<2 ,所以 f2n+1f2n=1 ,所以 f2n+1=f2n+1 ,将这个式子带入 (2) 中可以得到如下俩个等式

f2n=3fn

f2n+1=3fn+1

接下来我们可以发现,对于当前这个数我们可以进行两种操作:

  1. m 为奇数的时候,一定可以表达为fm=3×fm12+1
  2. 偶数时则是 fm=3×fm2+1 .

当我们第一个操作完后,接下来我们要进行第一步操作还是第二步操作取决于当前的奇偶,即二进制当前位是 1 还是0,这样我们就将 fn 变为如下等式:

fn=2_bitx×3x+2_bitx1×3x1+...+2_bit0×30

其中 2_bitx 表示 n 转换为二进制时的每一位.

然后我们就可以用数位DP来处理了,数组定义如下:

Rsum[i]:表示当前余数为 i 的数的个数
dp[x][y]:表示到达当前位置得到的余数为 y 的个数是多少
P[x]:用来预处理出每一位需要乘上多少个 3
vis[x]:用来标记当前位置是否一定访问过了
bit[x] :用来保存 n <script type="math/tex" id="MathJax-Element-5455">n</script>的每一位

代码

#include <cstdio>
#include <cstring>
#include <algorithm>


using namespace std;

typedef long long LL;
const int WS = 64 + 5;
const int REM = 65537 + 5;

LL n, dp[WS][REM], P[WS], Rsum[REM];
bool vis[WS];
int bit[WS], sz, k;

void pinit() {
    sz = 0;
    memset(dp, 0, sizeof(dp));
    memset(Rsum, 0, sizeof(Rsum));
    memset(vis, false, sizeof(vis));
    P[0] = 1;
    for(int i = 1; i < WS; i ++) {
        P[i] = P[i - 1] * 3 % k;
    }
}

void DFS(int pos, int remainder, int limit) {
    if(pos < 0) {
        Rsum[remainder] ++;
        return ;
    }
    //此处不再是用常规的dp[pos]...去标记是否访问的原因是,当前状态具有多种,即reaminder对余数个数的影响不同
    if(!limit && vis[pos]) {
        remainder = remainder * P[pos + 1] % k;
        for(int i = 0; i < k; i ++) {
            Rsum[(remainder + i) % k] += dp[pos][i];
        }
        return;
    }
    int ends = limit ? bit[pos] : 1;
    for(int i = 0; i <= ends; i ++) {
        DFS(pos - 1, (remainder * 3 + i) % k, limit && i == ends);
    }
    if(!limit) {
        vis[pos] = true;
        for(int i = 0; i < k; i ++) {
            dp[pos][i] = Rsum[i];//当前位置的状态即是Rsum[i]
        }
    }
}

void solve() {
    pinit();
    int len = 0;
    while(n) {
        bit[len ++] = n % 2;
        n >>= 1LL;
    }
    if(len == 0) len ++;
    DFS(len - 1, 0, 1);
    Rsum[0] --;//清除0对数据的影响
    LL ans = 0;
    for(int i = 0; i < k; i ++) {
        ans ^= Rsum[i];
    }
    printf("%lld\n", ans);
}

int T;

int main() {
    scanf("%d", &T);
    while(T --) {
        scanf("%lld%d", &n, &k);
        solve();
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值