感觉这东西还是挺玄学的
个人理解是靠状态数来控制复杂度
CF55D Beautiful numbers
题意:求区间中能被数位上每个数字整除的数的个数
l
,
r
<
=
1
0
18
l, r <= 10 ^ {18}
l,r<=1018
做法:数位dp经典问题
注意到
l
c
m
(
1
,
2
,
.
.
.
,
9
)
=
2520
lcm(1, 2,..., 9) = 2520
lcm(1,2,...,9)=2520, 所以途中只需记录模
2520
2520
2520所得的余数即可
d
p
[
p
o
s
]
[
m
o
d
]
[
l
i
m
i
t
]
dp[pos][mod][limit]
dp[pos][mod][limit]表示第pos位到末尾 前pos位模
2520
2520
2520所得余数为
m
o
d
mod
mod 当前出现数字的
l
c
m
lcm
lcm为
l
i
m
i
t
limit
limit的方案数
这题中学到一个小技巧,虽然在dp的时候需要考虑前面是否已经有一位比限制要小,但是状态中不需要记录这一维。原本以为记忆状态多是以空间换时间,但其实不记录这一维使多次询问之间的信息可以共享(现在想想如果记录这一维记忆化也没什么意义了)。数位dp时要注意,不是记忆的越具体越好,一定要让状态平凡到能让更多的询问共用。
虽然对于每个limit直接维护会使空间太大,但是其实可能成为limit的数只有50个左右,可以离散化处理。
复杂度:
m
a
x
(
O
(
n
)
)
=
d
i
g
i
t
[
n
]
∗
2520
∗
50
max(O(n)) = digit[n] * 2520 * 50
max(O(n))=digit[n]∗2520∗50
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
ll dp[21][3001][51], be, en, b[51], pre[3001];
int gcd(int x, int y)
{
return (y == 0) ? x : gcd(y, x % y);
}
void init()
{
int cnt = 0;
for(int i = 1; i <= 2520; i++)
if(2520 % i == 0)pre[i] = ++cnt;
}
ll solve(int p, int mod, int limit, int ok)
{
//printf("%d %d %d %d %d\n", p, mod, limit, ok, zero);
if(~dp[p][mod][pre[limit]] && ok)return dp[p][mod][pre[limit]];
if(p == 0)return (mod % limit == 0);
ll ans = 0;
if(ok)
{
for(int i = 0; i <= 9; i++)
{
int nl, nok;
nok = 1;
if(i != 0)nl = limit * i / gcd(limit, i); else nl = limit;
ans += solve(p - 1, (mod * 10 + i) % 2520, nl, nok);
}
}
else
{
for(int i = 0; i <= b[p]; i++)
{
int nl, nok;
if(i != b[p])nok = 1; else nok = 0;
if(i != 0)nl = limit * i / gcd(limit, i); else nl = limit;
ans += solve(p - 1, (mod * 10 + i) % 2520, nl, nok);
}
}
if(ok)dp[p][mod][pre[limit]] = ans;
return ans;
}
int getlen(ll x)
{
int j = 0;
while(x)
{
j++;
b[j] = x % 10;
x /= 10;
}
return j;
}
int main()
{
int t;
scanf("%d", &t);
init();
memset(dp, -1, sizeof(dp));
while(t--)
{
scanf("%I64d%I64d", &be, &en);
int l = getlen(en);
ll ans = solve(l, 0, 1, 0);
l = getlen(be - 1);
ans -= solve(l, 0, 1, 0);
printf("%I64d\n", ans);
}
return 0;
}
Scales
题意:有n个砝码,重量分别为
1
1
1,
2
2
2, …,
2
n
−
1
2^{n-1}
2n−1,还有一个重量为w的物品。从中选出一些砝码,求使天平平衡的方案个数。
n
<
=
1
0
6
n <= 10^6
n<=106
做法:一道相对独特的数位dp
首先,显然只放砝码无论如何不可能平衡
假设左物右码(中生物毒太深)
我们注意到,显然左边的砝码重量一旦确定,右边也随之确定。
于是我们从低往高枚举左边砝码重量每一位
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]表示第i位没有产生向前进位
d
p
[
i
]
[
1
]
dp[i][1]
dp[i][1]表示第i位产生了向前进位
转移即可
复杂度
O
(
n
)
O(n)
O(n)
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
int n, l, d;
char c[1000001];
int w[1000001], dp[1000001][2];
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%d%d%d", &n, &l, &d);
scanf("%s", c + 1);
for(int i = 1; i <= l; i++)
w[l - i + 1] = c[i] - '0';
for(int i = l + 1; i <= n; i++)
w[i] = 0;
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for(int i = 1; i <= n; i++)
{
if(w[i])
{
dp[i][0] = dp[i - 1][0];
dp[i][1] = (dp[i - 1][1] + dp[i - 1][0]) % d;
}
else
{
dp[i][0] = (dp[i - 1][0] + dp[i - 1][1]) % d;
dp[i][1] = dp[i - 1][1];
}
}
printf("%d\n", dp[n][0]);
}
return 0;
}
[ZJOI2010]数字计数
题意:求在[a,b]中的所有整数中,每个数码的出现次数
做法:裸数位dp
d
p
[
p
o
s
]
[
t
]
[
s
m
a
l
l
]
[
z
e
r
o
]
dp[pos][t][small][zero]
dp[pos][t][small][zero]表示当前位置为
p
o
s
pos
pos、所求数码为
t
t
t、是否已经比原数小、现在前面的位是否全是0
对每一个数码分开做即可
复杂度:
O
(
d
i
g
i
t
∗
10
∗
4
)
O(digit * 10 * 4)
O(digit∗10∗4),实际上跑的飞快
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 15;
long long f[N][N][2][2], cnt[N][2][2], p[N], pre[N];
long long dfs(int pos, int small, int zero, int t)
{
if(!pos)
{
cnt[pos][small][zero] = 1;
return 0;
}
if(~f[pos][t][small][zero])return f[pos][t][small][zero];
int x = ((small) ? 9 : p[pos]);
f[pos][t][small][zero] = 0;
int pd = (~cnt[pos][small][zero]);
if(!pd)cnt[pos][small][zero] = 0;
for(int i = 0; i <= x; i++)
{
int ns = (small || i != p[pos]);
int nz = (zero || i);
f[pos][t][small][zero] += dfs(pos - 1, ns, nz, t);
if(!pd)cnt[pos][small][zero] += cnt[pos - 1][ns][nz];
if(t == i && nz)f[pos][t][small][zero] += cnt[pos - 1][ns][nz];
}
return f[pos][t][small][zero];
}
long long dp(long long x, int y)
{
if(x <= 0)return 0;
int s = 0;
while(x)
{
p[++s] = x % 10;
x /= 10;
}
for(int i = 0; i <= s; i++)
f[i][y][0][0] = f[i][y][1][0] = f[i][y][0][1] = f[i][y][1][1] = -1;
memset(cnt, -1, sizeof(cnt));
return dfs(s, 0, 0, y);
}
int main()
{
long long a, b;
scanf("%lld%lld", &a, &b);
pre[0] = 1;
for(int i = 1; i <= N; i++)
pre[i] = pre[i - 1] * 10;
for(int i = 0; i <= 9; i++)
printf("%lld ", dp(b, i) - dp(a - 1, i));
return 0;
}
好少啊qwq
先把锅留下
(待续)