这一次有题解了!!
T1:CF54C First Digit Law
title
solution
这个题目是真的绕!!
其次本题是数位
d
p
dp
dp,概率
d
p
dp
dp,背包的结合
对于数位
d
p
dp
dp板块,其实我是没有写的,因为用简单的组合数就可以代替,显然
如果这个数长成
1
∗
∗
∗
∗
∗
∗
1******
1∗∗∗∗∗∗,那么如果位数小于这个数的就是可以乱填出
1
1
1的,
然后再加上
1000000
−
1
∗
∗
∗
∗
∗
∗
1000000-1******
1000000−1∗∗∗∗∗∗中的个数就好了
如果这个数长成 ( > 1 ) ∗ ∗ ∗ ∗ ∗ ∗ (>1)****** (>1)∗∗∗∗∗∗,那只要位数小于等于这个数都可以乱填
然后以取的区间个数作为重量,概率作为价值,乘号转移,然后自己领悟吧
code
#include <cstdio>
#define ll long long
int n, k;
double dp[1100], p[1100];
ll solve( ll num ) {
ll cnt = 0, last = 0, Pow = 1, ans = 0, x = num;
while( x ) {
last = x % 10;
cnt ++;
x /= 10;
}
for( int i = 1;i < cnt;i ++, Pow *= 10 )
ans += Pow;
if( last > 1 ) ans += Pow;
else if( last == 1 ) ans += num - Pow + 1;
return ans;
}
int main() {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ ) {
ll l, r;
scanf( "%I64d %I64d", &l, &r );
ll temp = solve( r ) - solve( l - 1 );
p[i] = temp * 1.0 / ( r - l + 1 );
}
scanf( "%d", &k );
dp[0] = 1.0;
for( int i = 1;i <= n;i ++ )
for( int j = n;~ j;j -- ) {
dp[j] = dp[j] * ( 1 - p[i] );
if( j > 0 ) dp[j] += dp[j - 1] * p[i];
}
double ans = 0;
for( int i = 0;i <= n;i ++ )
if( i * 100 >= n * k )
ans += dp[i];
printf( "%.12lf", ans );
return 0;
}
T2:CF509C Sums of Digits
title
solution
我们想一下怎么才能构造出最小的?显然
从低到高位开始填,一直填
9
9
9,到首位的时候就填剩下的那个数
r
r
r
因为数的大小首先是位数,其次是位数上的值!
第一个数肯定这样构造出来最小
接下来我们考虑后续数怎么构造出来
只有三种填法
①:仿照第一个数,也是后面全填
9
9
9,首位填剩的
②:在前一个构造出来的数上的首位
+
1
+1
+1,这样保证大于后,后面就乱填
③:在前一个构造出来的数上的任意一位增加至
9
9
9,直到满足数位和相等才停
显然,从低位往高位填是最小的
code
#include <cstdio>
int n, len;
int b[400], a[400];
void solve( int num ) {
for( int i = 1;num;i ++ ) {
while( a[i] < 9 && num )
a[i] ++, num --;
if( i > len && ! num ) len = i;
}
}
void print() {
for( int i = len;i;i -- )
printf( "%d", a[i] );
printf( "\n" );
}
int main() {
scanf( "%d", &n );
for( int i = 1;i <= n;i ++ )
scanf( "%d", &b[i] );
solve( b[1] );
print();
for( int i = 2;i <= n;i ++ ) {
int temp = b[i] - b[i - 1];
if( temp > 0 ) {
solve( temp );
print();
}
else {
int k = 1;
while( 1 ) {
if( k > len ) len = k;
if( a[k] < 9 && temp > 0 ) {
a[k] ++;
solve( temp - 1 );
print();
break;
}
temp += a[k];
a[k] = 0;
k ++;
}
}
}
return 0;
}
T3:CF431D Random Task
title
solution
首先看
n
n
n的范围
[
1
,
1
e
18
]
[1,1e18]
[1,1e18]我们想要把这个降下来,唯一支持的时间复杂度就是
l
o
g
N
logN
logN
自然而然就会想到去二分答案
n
n
n
但是二分必须要保证单调性,现在我们来证明一下n越大,二进制中有k个1的个数一定变大或不变
假设当前二分的答案为
n
n
n
那么计算范围就是
[
n
+
1
,
2
n
]
[n+1,2n]
[n+1,2n]
对于
n
+
1
n+1
n+1而言,我们主观是认为
n
+
1
n+1
n+1一定不劣于
n
n
n,有时候我们的主观是正确的
n
+
1
n+1
n+1计算范围为
[
n
+
2
,
2
n
+
2
]
[n+2,2n+2]
[n+2,2n+2]
分别把区间划分一下
[
n
+
1
,
2
n
]
=
n
+
1
,
[
n
+
2
,
2
n
]
[n+1,2n]=n+1,[n+2, 2n]
[n+1,2n]=n+1,[n+2,2n]
[
n
+
2
,
2
n
+
2
]
=
[
n
+
2
,
2
n
]
,
2
n
+
1
,
2
n
+
2
[n+2,2n+2]=[n+2,2n],2n+1,2n+2
[n+2,2n+2]=[n+2,2n],2n+1,2n+2
发现
[
n
+
2
,
2
n
]
[n+2,2n]
[n+2,2n]可以抵消
n
+
1
,
2
n
+
2
n+1,2n+2
n+1,2n+2也可以抵消!!
这个时候巧妙转化为二进制思考
2
n
+
2
2n+2
2n+2是
n
+
1
n+1
n+1的两倍,也就相当于
2
n
+
2
=
(
n
+
1
)
<
<
1
2n+2=(n+1)<<1
2n+2=(n+1)<<1
二进制左移以为不就相当补了一个
0
0
0,那么前面两个数中二进制含有
1
1
1的个数是不变的!!
所以也可以抵消
此时
[
n
+
1
,
2
n
+
2
]
[n+1,2n+2]
[n+1,2n+2]就还剩下一个
2
n
+
1
2n+1
2n+1,所以一定不劣于,证毕
所以为什么题目是
[
n
+
1
,
2
n
]
[n+1,2n]
[n+1,2n]范围,如果是
[
n
,
2
n
]
[n,2n]
[n,2n]就不好说了,而且要求必须恰好为
m
m
m不能大于
好了接下来,就是普通的数位
d
p
dp
dp了,先把套路搞起来
把
[
l
,
r
]
[l,r]
[l,r]转化为
[
1
,
r
]
−
[
1
,
l
−
1
]
[1,r]-[1,l-1]
[1,r]−[1,l−1]
然后用二进制数位
d
p
dp
dp
就是控制前
i
i
i位相等,然后后面乱填,这里不再赘述(你看代码就会马上get)
code
#include <cstdio>
#include <cstring>
#define ll long long
ll m;
int k, num[100];
ll dp[100][100];
ll dfs( int pos, int sum, bool lim ) {
if( ! pos ) return ( sum == k );
if( ! lim && dp[pos][sum] != -1 ) return dp[pos][sum];
int up = lim ? num[pos] : 1;
ll ans = 0;
for( int i = 0;i <= up;i ++ )
if( sum + ( i == 1 ) <= k )
ans += dfs( pos - 1, sum + ( i == 1 ), lim && ( i == up ) );
if( ! lim ) dp[pos][sum] = ans;
return ans;
}
ll solve( ll x ) {
int len = 0;
while( x ) num[++ len] = x & 1, x >>= 1;
return dfs( len, 0, 1 );
}
ll calc( ll x ) {
return solve( x << 1 ) - solve( x );
}
int main() {
scanf( "%lld %d", &m, &k );
memset( dp, -1, sizeof( dp ) );
ll l = 1, r = 1e18;
while( l <= r ) {
ll mid = ( l + r ) >> 1, ans = calc( mid );
if( ans == m ) return ! printf( "%lld", mid );
else if( ans > m ) r = mid - 1;
else l = mid + 1;
}
return 0;
}
T4:CF628D Magic Numbers
title
solution
这是道中规中矩的数位
D
P
DP
DP题目,只不过设计了一丁点的高精
按照套路我们会转化成
[
1
,
r
]
−
[
1
,
l
−
1
]
[1,r]-[1,l-1]
[1,r]−[1,l−1],但是
l
−
1
l-1
l−1涉及高精减,所以我们就单独拎出来
写一个
c
h
e
c
k
check
check专门判断
l
l
l是否符合,然后差分计算
[
1
,
r
]
−
[
1
,
l
]
[1,r]-[1,l]
[1,r]−[1,l]
注意,题目翻译是错误的
是从高位到低位(从左往右)首位是奇数,然后偶数位,奇数位,偶数位
这种看代码就会秒懂的,不展开叙述
code
#include <cstdio>
#include <cstring>
#define mod 1000000007
#define ll long long
int m, d, len;
ll ans;
int num[2005];
char l[2005], r[2005];
ll dp[2005][2005];
ll dfs( int pos, int sum, bool lim ) {
if( pos == -1 ) return ( sum == 0 );
if( ! lim && ~ dp[pos][sum] ) return dp[pos][sum];
int up = lim ? num[pos] : 9;
ll ans = 0;
for( int i = 0;i <= up;i ++ ) {
if( ( ( len - pos ) & 1 ) && i == d ) continue;
if( ( ! ( ( len - pos ) & 1 ) ) && i != d ) continue;
ans = ( ans + dfs( pos - 1, ( sum * 10 + i ) % m, lim && ( i == up ) ) ) % mod;
}
if( ! lim ) dp[pos][sum] = ans;
return ans;
}
ll calc( char *x ) {
len = strlen( x );
for( int i = 0;i < len;i ++ )
num[len - i - 1] = x[i] - '0';
return dfs( len - 1, 0, 1 );
}
bool check() {
len = strlen( l );
ll sum = 0;
for( int i = 0;i < len;i ++ ) {
int j = l[i] - '0';
if( ( ( i + 1 ) & 1 ) && j == d ) return 0;
if( ( ! ( ( i + 1 ) & 1 ) ) && j != d ) return 0;
sum = ( sum * 10 + j ) % m;
}
return ( sum == 0 );
}
int main() {
memset( dp, -1, sizeof( dp ) );
scanf( "%d %d %s %s", &m, &d, l, r );
ans = ( calc( r ) - calc( l ) + mod ) % mod;
ans += check();
printf( "%lld", ans % mod );
return 0;
}
T5:CF855E Salazar Slytherin’s Locket
title
solution
这个也是很简单的数位
D
P
DP
DP,只不过中间我们套一个状压
D
P
DP
DP
如果状态
s
s
s的第
i
i
i位为
0
0
0,表示数字
i
i
i出现了偶数次,反之出现奇数次
转化成
b
b
b进制直接搞就行了呗
code
#include <cstdio>
#include <cstring>
#define ll long long
int Q, b;
int num[70];
ll l, r;
ll dp[12][70][( 1 << 11 ) - 1];
ll dfs( int pos, int s, bool zero, bool lim ) {
if( !pos ) return !s;
if( ~dp[b][pos][s] && !zero && !lim ) return dp[b][pos][s];
int up = lim ? num[pos] : b - 1;
ll ans = 0;
for( int i = 0;i <= up;i ++ )
ans += dfs( pos - 1, ( !i && zero ) ? s : s ^ ( 1 << i ), zero && !i, lim && ( i == up ) );
if( !zero && !lim ) dp[b][pos][s] = ans;
return ans;
}
ll solve( ll x ) {
int len = 0;
while( x ) num[++ len] = x % b, x /= b;
return dfs( len, 0, 1, 1 );
}
int main() {
memset( dp, -1, sizeof( dp ) );
scanf( "%d", &Q );
while( Q -- ) {
scanf( "%d %lld %lld", &b, &l, &r );
printf( "%lld\n", solve( r ) - solve( l - 1 ) );
}
return 0;
}
T6:CF1245F Daniel and Spring Cleaning
title
solution
异或可以转化成二进制下不进位加法
朴素数位
D
P
DP
DP直接上
code
#include <cmath>
#include <cstdio>
#include <cstring>
#define ll long long
int T, L, R;
ll dp[40][2][2];
ll dfs( int pos, bool limL, bool limR ) {
if( pos == -1 ) return 1;
if( ~ dp[pos][limL][limR] ) return dp[pos][limL][limR];
dp[pos][limL][limR] = 0;
int upL = limL ? ( L >> pos ) & 1 : 1;
int upR = limR ? ( R >> pos ) & 1 : 1;
for( int i = 0;i <= upL;i ++ )
for( int j = 0;j <= upR;j ++ )
if( ! ( i & j ) )
dp[pos][limL][limR] += dfs( pos - 1, limL && ( i == upL ), limR && ( j == upR ) );
return dp[pos][limL][limR];
}
ll solve( int l, int r ) {
if( l == -1 ) return 0;
memset( dp, -1, sizeof( dp ) );
L = l, R = r;
dfs( log2( R + 1 ) + 1, 1, 1 );
}
int main() {
scanf( "%d", &T );
while( T -- ) {
int l, r;
scanf( "%d %d", &l, &r );
printf( "%lld\n", solve( r, r ) - solve( l - 1, r ) * 2 + solve( l - 1, l - 1 ) );
}
return 0;
}
T7:CF95D Horse Races
title
code
直接看代码即可
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define mod 1000000007
#define ll long long
int T, k;
int num[1005];
char l[1005], r[1005];
ll dp[1005][1005][2];
//pos表示当前位置
//d表示k+1减去与前一个幸运数字的距离
//那么当d降到0时,它与前面的幸运数字的距离就超过k了
//flag表示之前是否符合要求,符合要求为1,不符合为0
//lim表示当前位能否取到9,即之前是否达到上界,达到上界就为1,没有就为0
ll dfs( int pos, int d, bool flag, bool lim ) {
if( ! pos ) return flag;
if( ! lim && ~ dp[pos][d][flag] ) return dp[pos][d][flag];
int up = lim ? num[pos] : 9;
ll ans = 0;
for( int i = 0;i <= up;i ++ ) {
if( i != 4 && i != 7 )
ans = ( ans + dfs( pos - 1, max( d - 1, 0 ), flag, lim && ( i == up ) ) ) % mod;
else
ans = ( ans + dfs( pos - 1, k, flag || d, lim && ( i == up ) ) ) % mod;
}
if( ! lim ) dp[pos][d][flag] = ans;
return ans;
}
ll calc( char *x ) {
int len = strlen( x );
for( int i = len - 1;i >= 0;i -- )
num[len - i] = x[i] - '0';
return dfs( len, 0, 0, 1 );
}
bool check() {
int len = strlen( l ), last = 0x3f3f3f3f;
for( int i = len - 1;~ i;i -- ) {
int j = l[i] - '0';
if( j == 4 || j == 7 ) {
if( last - i <= k ) return 1;
else last = i;
}
}
return 0;
}
int main() {
memset( dp, -1, sizeof( dp ) );
scanf( "%d %d", &T, &k );
while( T -- ) {
scanf( "%s %s", l, r );
printf( "%lld\n", ( calc( r ) - calc( l ) + check() + mod ) % mod );
}
return 0;
}