1. 程式人生 > >數位DP 學習筆記2

數位DP 學習筆記2

題目HDU 4734 F(x):

題目大意是給你兩個數A,B,定義F(A)= 每個數位的數 * 2 ^ (位數 - 1)。求 0 - B 區間裡的 F(x) <= F(A) 的數字的個數。

一個數位DP的做法(TLE):

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[1005], FA, poww[15], dp[15][10005];
ll dfs(ll pos, ll lim, ll hav)
{
    if(hav > FA)return 0;
    if(pos == -1)return 1;
    if(lim == 0 && dp[pos][hav] != -1)return dp[pos][hav];
    ll len = lim ? a[pos] : 9, ans = 0, i;
    for(i = 0; i <= len; i++)
    {
        if(hav + i * poww[pos] <= FA)
            ans += dfs(pos - 1, lim && (i == len), hav + i * poww[pos]);
    }
    if(lim == 0)dp[pos][hav] = ans;
    return ans;
}
ll slove(ll n, ll m)
{
    FA = 0;
    ll pos = 0, k = 1, ans;
    while(n)
    {
        FA += k * (n % 10);
        n /= 10;
        k *= 2;
    }
    while(m)
    {
        a[pos++] = m % 10;
        m /= 10;
    }
    ans = dfs(pos - 1, 1, 0);
}
int main()
{
    ll t, n, m, i, j, k = 1, ca = 1;
    for(i = 0; i <= 13; i++)poww[i] = k, k *= 2;
    scanf("%lld", &t);
    while(t--)
    {
        scanf("%lld %lld", &n, &m);
        memset(dp, -1, sizeof(dp));
        ll ans  = slove(n, m);
        printf("Case #%lld: %lld\n", ca++, ans);
    }
    return 0;
}

儘管加了記憶化搜尋,但還是超時了,然後分析發現:

每次都要 memset(dp),用了很多時間,然後想辦法優化它。

如果我們把擁有價值改變一下,變成剩餘價值,即不再是它有多少,而是它還能加多少價值,這樣,它的邊界不再是可變的F(A),而是固定的值(0)。這樣,便可以只memset(dp)一次,不需要每組都初始化了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[15];
ll dp[15][200005];
ll po[15], maxx;
ll F(ll x)
{
    ll pos = 0, ans = 0, num, pi = 1;
    while(x)
    {
        num = x % 10;
        x /= 10;
        ans += num * pi;
        pi *= 2;
    }
    return ans;
}
ll dfs(ll pos, ll sta, ll lim)
{
    if(sta < 0)return 0;
    if(pos == -1)return 1;
    if(lim == 0 && dp[pos][sta] != -1)return dp[pos][sta];
    ll len = lim ? a[pos] : 9;
    ll ans = 0;
    for(ll i = 0; i <= len; i++)
    {
        ll w = po[pos] * i;
        if(sta - w < 0)continue;
        else
        {
            ans += dfs(pos - 1, sta - w, lim && i == a[pos]);
        }
    }
    if(lim == 0)dp[pos][sta] = ans;
    return ans;
}
ll slove(ll x)
{
    ll pos = 0;
    while(x)
    {
        a[pos++] = x % 10;
        x /= 10;
    }
    ll ans = dfs(pos - 1, maxx, 1);
    return ans;
}
int main()
{
    ll t, k, i, j, m, n;
    k = 1;
    for(i = 0; i <= 15; i++)po[i] = k, k *= 2;
    scanf("%lld", &t);
    for(ll q = 0; q < 15; q++)
    {
        for(ll w = 0; w < 200005; w++)dp[q][w] = -1;
    }
    for(i = 1; i <= t; i++)
    {

        scanf("%lld %lld", &n, &m);
        maxx = F(n);
        //cout << maxx << endl;
        ll ans = slove(m);
        if(0 <= maxx)ans++;
        printf("Case #%lld: %lld\n",i, ans - 1);
    }
    return 0;
}

總結:遇到超時問題時可以試著轉換方法,反過來考慮。