A - 喵喵的数字
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
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 。