2021/01/19訓練總結
文章目錄
前言
昨天進行了一場數位dp專題,寫下題解。如果要學習數位dp,這裡給一個我學習的連結。
關於數位dp的一些題:
P4999 煩人的數學作業
P6218 [USACO06NOV] Round Numbers S
P2657 [SCOI2009] windy 數
P4317 花神的數論題
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]
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) % (9!));
}
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;
}