動規-數位DP
阿新 • • 發佈:2018-09-25
span std || mes using set zjoi2010 get long
某場練習賽中由於沒寫過數位 DP 板子(OrzOrz),只能分段打表亂搞,心態非常崩。當時想的是二分數位的每一位,這樣會非常繞,可不可行不知道,但現在我還沒有想出用那種二分的解法。其實是要對數字範圍二分,然後 DP 驗證合理的數個數就可以了。然後補練了一下幾道題,感覺數位 DP 不難,主要是狀態設計(是否卡邊界、最高位是否從1開始、討論到哪位)的套路吧,不知道套路先推還是有點危險噠。另外據說正統寫法並沒有 [最高位是否從1開始] 這一維,而是去討論前導 0 。我覺得那樣有點難想,所以就加了一維。
【ZJOI2010 Day1】數字計數
題目大意:求 \([L,\ R]\) 中, [0-9] 數碼出現的次數。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; //i wei //h 是邊界 //g 從1開始 //x y已經出現的個數 //y 求y的個數 ll f[13][2][2][13][10], ans[10]; int num[13]; ll qwq(int n, bool h, bool g, int x, int y) { ll &t = f[n][h][g][x][y]; if (~t) return t; if (!n) return x; t = 0; int lll = g, rrr = h ? num[n] : 9; for (int i = lll; i <= rrr; ++i) { t += qwq(n - 1, h && i == rrr, false, x + (i == y), y); } return t; } void getans(ll n, bool t) { int top = 0; do num[++top] = n % 10; while (n /= 10); memset(f, -1, sizeof f); for (int i = 1; i <= top; ++i) for (int j = 0; j <= 9; ++j) if (t) ans[j] += qwq(i, i == top, true, 0, j); else ans[j] -= qwq(i, i == top, true, 0, j); return; } int main() { ll A, B; scanf("%lld%lld", &A, &B); getans(B, true); if (A > 1) getans(A - 1, false); for (int i = 0; i <= 9; ++i) printf("%lld ", ans[i]); return 0; }
【SCOI2009 Day1】windy數
題目大意:求 \([L,\ R]\) 區間相鄰兩個數字差的絕對值均 \(\ge 2\) 的數的個數。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; ll f[11][11][2][2]; int num[11]; //f[i][a][g][h] //i //a 上一位 //g 是邊界 //h 從1開始 ll qaq(int n, int a, bool g, bool h) { ll &t = f[n][a][g][h]; if (~t) return t; t = 0; int lll = h, rrr = g ? num[n] : 9; if (!n) return t = 1; for (int i = lll; i <= rrr; ++i) { if (a != 10 && abs(i - a) < 2) continue; t += qaq(n - 1, i, g && i == rrr, false); } return t; } ll getans(ll n) { int top = 0; memset(f, -1, sizeof f); do num[++top] = n % 10; while (n /= 10); ll t = 0; for (int i = 1; i <= top; ++i) t += qaq(i, 10, i == top, true); return t; } int main() { ll L, R; scanf("%lld%lld", &L, &R); ll t = getans(R); if (L > 1) t -= getans(L - 1); printf("%lld\n", t); return 0; }
土撥鼠獵人
題目大意:求第 \(k\) 小的特殊數。特殊數是指不滿足 \(\left( [有連續兩位相等] \bigwedge [有數字等於右邊數字+1] \right) \bigvee [有連續三位是233]\) 的數。二分, DP 計算 \(\le\) 二分答案的合理的數個數即可。
//f[i][a][b][x][y][z]
//x 相等
//y 等於前一位+1
//z 是邊界
//g 從1開始
#include <cstdio>
#include <ctype.h>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
ll f[20][12][12][2][2][2][2];
int num[20];
ll qaq(int n, int a, int b, bool x, bool y, bool z, bool g)
{
ll &t = f[n][a][b][x][y][z][g];
if (~t) return t;
if (x && y) return t = 0;
if (!n) return t = 1;
t = 0;
int lll = g, rrr = z ? num[n] : 9;
for (int i = lll; i <= rrr; ++i) {
if (i == 3 && a == 3 && b == 2) continue;
t += qaq(n - 1, i, a, x || i == a, y || i + 1 == a, z && i == rrr, false);
}
return t;
}
ll check(ll n)
{
if (n <= 99) return n;
memset(f, -1, sizeof f);
int top = 0;
do num[++top] = n % 10;
while (n /= 10);
ll t = 0;
for (ll i = top; i >= 1; --i)
t += qaq(i, 11, 11, false, false, i == top, true);
return t;
}
int main()
{
ll N;
scanf("%lld", &N);
ll l = N, r = 1e18 + 5000, ans = 1;
while (l <= r) {
ll mid = l + r >> 1;
if (check(mid) >= N)
ans = mid, r = mid - 1;
else
l = mid + 1;
}
printf("%lld\n", ans);
return 0;
}
動規-數位DP