【数位DP】ACdream原创群赛(15)の每题10s多开心 A - 喵喵的数字

A - 喵喵的数字

Time Limit:  20000/10000MS (Java/Others)  Memory Limit:  512000/256000KB (Java/Others)
Problem Description

喵喵喜欢数字~

但是她觉得数字总是按照 1 2 3 ....... n 这样排列太老土了!!!哼

她无意间看到了 Matrix67 的一篇blog(太神了。。)

int f(int x){
    int b , t , c , m , r;
    b = x & -x;
    t = x + b;
    c = t ^ x;
    m = (c >> 2) / b;
    r = t | m; //最终结果
    return r;
}

她觉得实在是太神了啊!!这段代码的作用你可以自己尝试一下,不过喵喵自己尝试了一发是对于每个x输出下一个y,满足 y 和 x 拆成二进制表示之后 1 的个数相等!

比如 6 的二进制表示是 (110)2 那么下一个数的二进制表示就是(1001)2 也就是9.

于是喵喵对于选定的一个数 N , 她把所有 [0 , N] (闭区间) 中的数依据如下算法重新排列了一下

operator (int A ,  int B){
    if (A 的 二进制表示中 1 的个数 > B 的 二进制表示中 1 的个数)  那么 A 比 B 大;
    else if (A 的 二进制表示中 1 的个数 < B 的 二进制表示中 1 的个数) 那么 B 比 A 大;
    else if (A < B) 那么 B 比 A 大;
    else if (A > B) 那么 A 比 B 大;
    else A 和 B 相等
}

比如 N = 8 的时候,数字的顺序是

0,1,2,4,8,3,5,6,7

由此,她想知道 对于 给定的 N ,第 M 小的数是几,前 M 小的数和是多少。(注意,第 0 小的一定是 0 , 第 1 小的才是 1)。

因为和神马的实在是太大了,喵喵只需要你告诉他答案对于 (109 + 9) 取模的答案就行啦。

P.S. 某次 TC 就是 109 + 9 坑出翔了,再错就打屁股啦!

0≤N≤1018 , 0≤M≤N

Input

第一行一个整数 T 代表数据组数。(T≤10000)                      <del>喵以人格担保时限肯定够!!!</del>

对于每组数据第一行两个整数 N , M。

Output
对于每组询问,输出两个整数 A , B 代表 “第 M 小的数” 和 “前 M 小的数字和 对于 (10 9 + 9) 取模” 的答案。(第一问不要取摸)
Sample Input
9
8 0
8 1
8 2
8 3
8 4
8 5
8 6
8 7
8 8
Sample Output
0 0
1 1
2 3
4 7
8 15
3 18
5 23
6 29
7 36

A
居然有人直接用代码水!!太不人道太不科学了啊!!!这题代码没 2000B 能看?
正解:
枚举数字中 1 的个数,以 dp[第几位][还剩下几个1需要消耗] 的状态进行数位dp。
如果X 个 1 的个数有 A 个,我们求第 M 个数,那么就 M = M – A , 然后枚举有如果X + 1 个 1 的有多少个。
否则在数位dp 上面查找恰好第 M 个是几。
复杂度为 70 * 70 (状态数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <cassert>
using namespace std;
const int MOD = 1e9 + 9;
const int TAT = 70;
typedef long long LL;
int Case;
 
namespace NT{
     inline void INC(LL &a, LL b){a += b;  if (a >= MOD) a -= MOD;}
     inline LL sum(LL a, LL b){a += b;  if (a >= MOD) a -= MOD;  return a;}
     inline void DEC(LL &a, LL b){a -= b;  if (a < 0) a += MOD;}
     inline LL dff(LL a, LL b){a -= b;  if (a < 0) a  += MOD;  return a;}
     inline void MUL(LL &a, LL b){a = (LL)a * b % MOD;}
     inline LL pdt(LL a, LL b){ return (LL)a * b % MOD;}
} using namespace NT;
 
LL dp[TAT][TAT];
LL cnt[TAT][TAT];
LL num[TAT];
LL two[TAT];
LL dfscnt( int i,  int s,  bool e) {
     if (i==-1)  return s==0;
     if (!e && ~cnt[i][s])  return cnt[i][s];
     LL res = 0;
     int u = e?num[i]:1;
     for ( int d = 0; d <= u; ++d)  if (s - d >= 0)
         res += dfscnt(i-1, s - d, e&&d==u);
     return e?res:cnt[i][s]=res;
}
LL dfssum( int i ,  int s ,  bool e){
     if (i == -1)  return 0;
     if (!e && ~dp[i][s])  return dp[i][s];
     LL res = 0;
     int u = e ? num[i] : 1;
     for ( int d = 0 ; d <= u ; ++d)  if (s - d >= 0){
         LL has = dfscnt(i-1, s - d ,  e&&d==u);
         LL tmp = 0;
         if (d) tmp = two[i] % MOD * (has % MOD) % MOD; //pdt(two[i] % MOD , has % MOD);
         INC(res , tmp);
         INC(res , dfssum(i - 1 , s - d , e && d == u));
     }
     return e ? res : dp[i][s] = res ;
}
int expand(LL x){
     int y = 0;
     while (x){
         num[y++] = x & 1;
         x >>= 1;
     }
     return y - 1;
}
LL dfsfind( int i ,  int s , LL k , LL me ,  bool e){
     if (i==-1)  return me;
     LL res = 0;
     int u = e?num[i]:1;
     for ( int d = 0; d <= u; ++d)  if (s >= d){
         res = dfscnt(i-1, s - d, e&&d==u);
//        cout << i << ' ' << d << ' ' << (res >= k) << ' ' << me << endl;
         if (res >= k)  return dfsfind(i - 1 , s - d , k , me | (1ll * d * two[i]), e && d == u);
         k -= res;
     }
     assert (0);
}
LL findmeeasy( int head , LL n , LL m){
     int y = expand(n);
     return dfsfind(y , head , m , 0 , 1);
}
void solve(){
     LL n , m;
     scanf ( "%lld%lld" , &n , &m);
     if (m == 0){
         puts ( "0 0" );
         return ;
     }
     int head = 1;
     LL ans = 0;
     int y = expand(n);
     memset (dp , -1 ,  sizeof (dp));
     memset (cnt , -1 ,  sizeof (cnt));
     for ( ; ; head++){
         assert (head < TAT);
         LL res = dfscnt(y , head , 1);
         if (res >= m)  break ;
         m -= res;
         INC(ans , dfssum(y , head , 1));
     }
//    LL ret = findme(head , n , m);
     LL ret = findmeeasy(head , n , m);
     memset (cnt , -1 ,  sizeof (cnt));
     memset (dp , -1 ,  sizeof (dp));
     y = expand(ret);
//    cout << ans << endl;
     INC(ans , dfssum(y , head , 1));
     printf ( "%lld %lld\n" , ret , ans);
//    cerr << ++Case << endl;
}
int main(){
//    freopen("00.in" , "r" , stdin);
//    freopen("00.out" , "w" , stdout);
     two[0] = 1;
     for ( int i = 1 ; i < TAT ; ++i)
         two[i] = (two[i - 1] << 1);
     Case = 0;
     int _;
     scanf ( "%d" , &_);
     while (_--) solve();
     return 0;
}


TLE 解:
虽然还是 dp[第几位][还剩下几个1] 。 如果我们二分答案,分别计算那么复杂度需要乘以 log(1e18) 这个复杂度是被卡掉的,会 TLE 。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值