文章目录
前言
昨天进行了一场数位dp专题,写下题解。如果要学习数位dp,这里给一个我学习的链接。
关于数位dp的一些题:
P4999 烦人的数学作业
P6218 [USACO06NOV] Round Numbers S
P2657 [SCOI2009] windy 数
P4317 花神的数论题
P2602 [ZJOI2010]数字计数
P4127 [AHOI2009]同类分布
P4124 [CQOI2016]手机号码
HDU 2089 不要62
题目链接:不要62
题目大意:给定
[
L
,
R
]
[L,R]
[L,R]区间,问区间内不含62和4的数字的个数。
数据范围:
0
<
n
≤
m
<
1000000
0<n \le m <1000000
0<n≤m<1000000
题解:这个数据量不是很大,所以可以直接暴力
L
→
R
L\to R
L→R扫一遍,看是否有62
o
r
or
or 4。不过还是说一下我数位
d
p
dp
dp的做法,其实简单数位
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]。然后用
f
i
n
d
(
x
)
find(x)
find(x)函数来找
[
1
,
x
]
[1,x]
[1,x]内满足要求的个数。我们需要在dfs中维护的有:还剩余的位数,是否顶着上界,搜索的前一个数,是否已经包含62,是否包含了4。然后我们对应转移就行了。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[15], dp[10][2][10][2][2];
int dfs(int cur, int limit, int pre, bool _4, bool _62)
{
if (!cur)
{
if (_4 || _62)return 0;
return 1;
}
if (~dp[cur][limit][pre][_4][_62])return dp[cur][limit][pre][_4][_62];
int ans = 0;
int tp = limit ? w[cur] : 9;
for (int i = 0; i <= tp; i++)
{
ans += dfs(cur - 1, limit && i == tp, i, _4 || i == 4, _62 || (i == 2 && pre == 6));
}
return dp[cur][limit][pre][_4][_62] = ans;
}
int find(int x)
{
memset(dp, -1, sizeof(dp));
w[0] = 0;
while (x)w[++w[0]] = x % 10, x /= 10;
return dfs(w[0], 1, 0, 0, 0);
}
signed main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
while (1)
{
int l, r; read(l), read(r);
if (!l && !r)break;
printf("%lld\n", find(r) - find(l - 1));
}
return 0;
}
HDU - 3555 Bomb
题目链接:Bomb
题目大意:求
[
L
,
R
]
[L,R]
[L,R]区间内包含
49
49
49的有多少。
数据范围:
1
≤
T
≤
1
0
5
,
1
≤
N
≤
2
63
−
1
1 \le T \le 10^5,1\le N \le 2^{63}-1
1≤T≤105,1≤N≤263−1
题解:我们可以发现,不同的数位dp基本上改的就是dfs函数。这题我们就要维护前面的搜索结果里面是否已经包含了49,我这里用
i
s
o
k
isok
isok表示。然后要维护
i
s
o
k
isok
isok,我们需要得到上一次的搜索数位是什么,我们用
p
r
e
pre
pre来记录。剩下的就是对应转移了。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int w[30], dp[30][2][10][2],t;
int dfs(int cur, bool limit, int pre, bool isok)
{
if (!cur)
{
return isok;
}
if (~dp[cur][limit][pre][isok])return dp[cur][limit][pre][isok];
int ans = 0;
int tp = limit ? w[cur] : 9;
for (int i = 0; i <= tp; i++)
{
ans += dfs(cur - 1, limit && (i == tp),i, isok||(i == 9 && pre==4));
}
return dp[cur][limit][pre][isok] = ans;
}
int find(int x)
{
memset(dp, -1, sizeof(dp));
w[0] = 0;
while (x)w[++w[0]] = x % 10, x /= 10;
return dfs(w[0], 1, 0, 0);
}
signed main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
read(t);
while (t--)
{
int x; read(x);
printf("%lld\n", find(x));
}
return 0;
}
CodeForces - 55D Beautiful numbers
题目链接:Beautiful numbers
题目大意:求
[
L
,
R
]
[L,R]
[L,R]区间内包含可以被自身所有数位整除的有多少。
数据范围:
1
≤
t
≤
10
,
1
≤
l
≤
r
≤
9
∗
1
0
18
1\le t \le 10,1\le l\le r\le 9*10^{18}
1≤t≤10,1≤l≤r≤9∗1018
题解:非常好的一道题,数位dp进阶必做。首先我们依靠前两题来写一下我们的模板dfs函数
int dfs(int cur, bool limit, int premul, int prenum)
{
if (!cur)
{
return prenum % premul == 0;
}
if (~dp[cur][limit][premul][prenum])return dp[cur][limit][premul][prenum];
int ans = 0;
int tp = limit ? w[cur] : 9;
for (int i = 0; i <= tp; i++)
{
ans += dfs(cur - 1, limit && (i == tp), !i?premul:premul*i, (prenum * 10 + i));
}
return dp[cur][limit][premul][prenum] = ans;
}
和我们之前写的完全一样! p r e n u m prenum prenum表示之前搜索的数, p r e m u l premul premul表示已经搜索的数位的积。这样我们就完成了这一题。不过有个问题就是dp数组的大小。我们 p r e m u l premul premul可以达到 9 ∗ 1 0 18 9*10^{18} 9∗1018的大小!空间已经炸穿了。我们这里可以发现一个空间小优化就是我们可以预处理出 9 ! 9! 9!,边模边运算。dfs如下
int dfs(int cur, bool limit, int premul, int prenum)
{
if (!cur)
{
return prenum % premul == 0;
}
if (~dp[cur][limit][premul][prenum] && !limit)return dp[cur][limit][premul][prenum];
int ans = 0;
int tp = limit ? w[cur] : 9;
for (int i = 0; i <= tp; i++)
{
ans += dfs(cur - 1, limit && (i == tp), !i ? premul : premul * i, (prenum * 10 + i) % (9!));
}
return dp[cur][limit][premul][prenum] = ans;
}
这样空间要求变成了 19 ∗ 2 ∗ 9 ! ∗ 9 ! 19*2*9!*9! 19∗2∗9!∗9!掐指一算,我们还是炸空间了,然后可以可以注意到一个优化的点,就是我们可以将 p r e m u l premul premul变成 p r e l c m prelcm prelcm也就是将之前搜索到的数位积变成数位lcm。这样我们就可以将9!优化下到 l c m ( 1 , 2 , . . 9 ) = 2520 lcm(1,2,..9)=2520 lcm(1,2,..9)=2520。dfs如下
int dfs(int cur, bool limit, int prelcm, int prenum)
{
if (!cur)
{
return prenum % prelcm == 0;
}
if (~dp[cur][limit][prelcm][prenum] && !limit)return dp[cur][limit][prelcm][prenum];
int ans = 0;
int tp = limit ? w[cur] : 9;
for (int i = 0; i <= tp; i++)
{
ans += dfs(cur - 1, limit && (i == tp), !i ? prelcm : lcm(prelcm ,i), (prenum * 10 + i) % (2520));
}
return dp[cur][limit][prelcm][prenum] = ans;
}
空间成功变成 19 ∗ 2 ∗ 2520 ∗ 2520 19*2*2520*2520 19∗2∗2520∗2520。还是超了一点。。。不过我们可以发现 p r e l c m prelcm prelcm无法取到 1 → 2520 1\to 2520 1→2520之间的所有数,只能取到2520的因子数。所以最后一个优化就是将 p r e l c m prelcm prelcm离散化。离散化后可以发现 p r e l c m prelcm prelcm只能取48个,所以我们空间变成了 19 ∗ 2 ∗ 50 ∗ 2520 19*2*50*2520 19∗2∗50∗2520,完全ok了。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
#define int long long
const ll N = 200000 + 10;
int w[30], dp[20][50][2600], t, totmod;
int a[N], toa[N];
int gcd(int x, int y)
{
return !y ? x : gcd(y, x % y);
}
int lcm(int x, int y)
{
if (!x || !y)return x + y;
return x * y / gcd(x, y);
}
void init()
{
memset(dp, -1, sizeof(dp));
totmod = 1;
for (int i = 2; i <= 9; i++)totmod = lcm(totmod, i);
for (int i = 1; i <= totmod; i++)
{
if (totmod % i == 0)a[++a[0]] = i, toa[i] = a[0];
}
}
int dfs(int cur, bool limit, int prelcm, int prenum)
{
if (!cur)
{
return prenum % prelcm == 0;
}
if (~dp[cur][toa[prelcm]][prenum]&&!limit)return dp[cur][toa[prelcm]][prenum];
int ans = 0;
int tp = limit ? w[cur] : 9;
for (int i = 0; i <= tp; i++)
{
ans += dfs(cur - 1, limit && (i == tp), lcm(prelcm, i), (prenum * 10 + i) % totmod);
}
if (!limit)dp[cur][toa[prelcm]][prenum] = ans;
return ans;
}
int find(int x)
{
w[0] = 0;
while (x)w[++w[0]] = x % 10, x /= 10;
return dfs(w[0], 1, 1, 0);
}
signed main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
init();
read(t);
while (t--)
{
int l, r; read(l), read(r);
printf("%lld\n", find(r) - find(l - 1));
}
return 0;
}
LightOJ - 1336 Sigma Function
题目链接:Sigma Function
题目大意:给定n,求[1-n]中因子个数为偶数的个数。
数据范围:
1
≤
t
≤
100
,
1
≤
n
≤
1
0
12
1\le t \le 100,1\le n\le10^{12}
1≤t≤100,1≤n≤1012
题解:我是直接打表写的。程序如下
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int calc(int x)
{
int ans = 0;
for (int i = 1; i * i <= x; i++)
{
if (x % i == 0)
{
ans += i;
if (x != i * i)ans += x / i;
}
}
return ans;
}
ll t, n;
int main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
for (int i = 1; i <= 1000; i++)
{
if(calc(i)%2)
printf("%d ", i);
}
return 0;
}
结果如下:
我发现因子个数为奇数的i都满足
i
=
2
p
∗
x
∗
x
i=2^p*x*x
i=2p∗x∗x
进一步分解,假设p是偶数则有
y
=
x
∗
2
p
2
,
i
=
y
∗
y
y=x*2^{\frac{p}{2}},i=y*y
y=x∗22p,i=y∗y
p是奇数则有
y
=
x
∗
2
p
2
,
i
=
2
∗
y
∗
y
y=x*2^{\frac{p}{2}},i=2*y*y
y=x∗22p,i=2∗y∗y
即如果i满足是完全平方数
o
r
or
or完全平方数的两倍则i的因子个数数是奇数。
又完全平方数的个数=
n
\sqrt{n}
n,完全平方数的两倍个数=
n
2
\sqrt{\frac{n}{2}}
2n。所以
a
n
s
=
n
−
n
−
n
2
ans=n-\sqrt{n}-\sqrt{\frac{n}{2}}
ans=n−n−2n
这里有一个证明的题解:链接
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
ll t, n;
int main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
ll t, n; int qiu = 0;
read(t);
while (t--)
{
read(n);
printf("Case %d: %lld\n", ++qiu, n - (ll)sqrt(n) - (ll)sqrt(n / 2));
}
return 0;
}
HDU - 4352 XHXJ’s LIS
题目链接:C - XHXJ’s LIS
题目大意:给定[L,R],求各位数字组成的严格上升子序列的长度为K的个数。
数据范围:
1
≤
L
≤
R
≤
2
63
−
1
,
1
≤
K
≤
10
1\le L\le R\le 2^{63}-1,1\le K \le 10
1≤L≤R≤263−1,1≤K≤10
题解:我们用一个10位二进制状态来表示最长上升子序列,状态的修改参考导弹拦截题目里面给出的
n
l
o
g
n
nlogn
nlogn修改的方法。其他的就是数位dp了。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
#define int long long
const ll N = 200000 + 10;
const int mod = 1e9 + 7;
int t, l, r, K;
int dp[20][2][1 << 11][11], w[21];
int getnum(int x)
{
int ans = 0;
while (x)
{
ans++;
x -= x & -x;
}
return ans;
}
int updatestate(int x, int st)
{
for (int i = x; i <= 9; i++)
{
if (st & (1 << i))return (st ^ (1 << i)) | (1 << x);
}
return st | (1 << x);
}
int dfs(int cur, int limit, int _0, int state)
{
if (!cur)return getnum(state) == K;
if (!limit && ~dp[cur][_0][state][K])return dp[cur][_0][state][K];
int ans = 0;
int tp = limit ? w[cur] : 9;
for (int i = 0; i <= tp; i++)
{
ans += dfs(cur - 1, limit && i == tp, _0 && i == 0, (_0 && i == 0) ? 0 : updatestate(i, state));
}
if (!limit)dp[cur][_0][state][K] = ans;
return ans;
}
int find(int x)
{
w[0] = 0;
while (x)w[++w[0]] = x % 10, x /= 10;
return dfs(w[0], 1, 1, 0);
}
signed main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
memset(dp, -1, sizeof(dp));
read(t); int qiu = 0;
while (t--)
{
read(l), read(r), read(K);
printf("Case #%lld: %lld\n", ++qiu, find(r) - find(l - 1));
}
return 0;
}
LightOJ - 1282 Leading and Trailing
题目链接:Leading and Trailing
题目大意:求
n
k
n^k
nk的前3位和后3位
数据范围:
2
≤
n
≤
2
31
,
1
≤
k
≤
1
0
7
2\le n \le 2^{31},1\le k \le 10^7
2≤n≤231,1≤k≤107
题解:参考链接
后三位我们可以直接快速幂模1000求出,对于前三位
令
1
0
p
=
n
k
10^p=n^k
10p=nk其中
p
=
l
o
g
10
(
n
k
)
=
k
∗
l
o
g
10
(
n
)
=
m
+
x
p=log10(n^k)=k*log10(n)=m+x
p=log10(nk)=k∗log10(n)=m+x,
m
m
m为
p
p
p的整数部分,
x
x
x为
p
p
p的小数部分。写成科学计数法就是
1
0
x
∗
1
0
m
=
n
k
10^x*10^m=n^k
10x∗10m=nk其中
1
0
x
10^x
10x大于1小于10,
m
m
m是决定值的位数。也就是我们的前3位只和
1
0
x
10^x
10x有关,
1
0
x
∗
100
10^x*100
10x∗100再取整就是答案了。
AC代码:
#include<bits/stdc++.h>
#define ld long double
#define ll long long
using namespace std;
template<class T>
void read(T& x)
{
T res = 0, f = 1; char c = getchar();
while (!isdigit(c)) {
if (c == '-')f = -1; c = getchar();
}
while (isdigit(c)) {
res = (res << 3) + (res << 1) + c - '0'; c = getchar();
}
x = res * f;
}
const ll N = 200000 + 10;
const int mod =1000;
ll t, n, k;
int fpow(ll x, ll y)
{
x %= mod;
int ans = 1;
while (y)
{
if (y & 1)ans = 1ll * x * ans % mod;
x = 1ll * x * x % mod; y >>= 1;
}
return ans;
}
int main()
{
//ios::sync_with_stdio(false);
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
#endif // ONLINE_JUDGE
read(t); int qiu = 0;
while (t--)
{
read(n), read(k);
double p = k * log10(n);
double x = p - int(p);
double tx = pow(10, x);
printf("Case %d: %03d %03d\n", ++qiu, (int)(tx * 100), fpow(n, k));
}
return 0;
}