2019HDU多校第六场 - 11 Dimensions HDU - 6644(思维+数位dp+康拓展开思想)

26 篇文章 2 订阅
13 篇文章 2 订阅

题意:

链接:HDU - 6644

给你一个长度为 n 的数字,但是把其中的某些位使用 "?" 遮住了,然后让你把 "?" 遮住的部分填上数字,使得填填上后的数字 n ,满足 n % m == 0 。 有 q 次询问,每次询问要你输出满足条件的第 k 小的 n。

n,m,q(1≤n≤50000,2≤m≤20,1≤q≤100000) T(1≤T≤10000) k(1≤k≤10^18) 

It is guaranteed that ∑n≤500000  and ∑q≤10^6 。

解题思路:

这里利用了一个比较好的思维方式,转化一下求解对象,比如给你一个 n 是:32???5? ,你可以把他看成 3200050 + ???0? ,然后你求出 3200050 % m = a。 那么其实你就只需要让 ???0? (? 代表 0 - 9 的数字) 满足 ???0? % m = m - a 。然后构造一个dp数组来描述状态,可以令 dp[ i ][ j ] 代表从右边起,填到第 i 个 ?时取模 m 为 j 的方案数。求完dp数组之后,根据他要求的 k ,从高位到低位枚举每一位,按照康拓展开的思路去求解答案,不过询问次数太多,如果每次都要把所有的 ?位都枚举了会T掉,不过呢其实用不到枚举那么多位置,因为 k 最大才 10 ^ 18 而且 m 很小,才 20。所以如果对于 ? 的个数超过30的,你其实只需要枚举低位的30个?就足够了,因为30个?就可以形成 10 ^ 30 种值,应该会把答案都覆盖进去,第30个 ?以后的都填 0 就可以。具体细节可以参照代码理解。

AC代码:

#include<bits/stdc++.h>
#define up(i, x, y) for(ll i = x; i <= y; i++)
#define down(i, x, y) for(ll i = x; i >= y; i--)
#define bug prllf("*********\n")
#define debug(x) cout<<#x"=["<<x<<"]" <<endl
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll maxn = 1e5 + 7;
const double pi = acos(-1);
const ll INF = 0x3f3f3f3f3f3f3f3f;
using namespace std;

ll n, m, q, k, len, cnt, tmm, tmod, len_0;
ll pos[maxn], pm[maxn], pmod[maxn];
ll dp[maxn][25];
char str[maxn];

ll power(ll a, ll b, ll mod)
{
    ll ans = 1;
    while(b)
    {
        if(b & 1)
            ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

int main()
{
    ll T; scanf("%lld", &T); while(T--)
    {
        tmm = 0, tmod = 0;
        len_0 = 0;

        scanf("%lld %lld %lld", &n, &m, &q);
        scanf("%s", str + 1);
        len = strlen(str + 1);
        for(ll i = 1; i <= len; i++)
        {
            tmm = tmm * 10 % m;  // 预处理出模数,以后直接在该基础上运算
            tmod = tmod * 10 % mod;  // 预处理出模数,以后直接在该基础上运算
            if(str[i] != '?')
            {
                tmm += str[i] - '0';
                tmod += str[i] - '0';
            }
            else
            {
                pos[++len_0] = len - i;
                pm[len_0] = power(10, len - i, m);  // 打表
                pmod[len_0] = power(10, len - i, mod);
            }
        }
        tmm %= m;
        tmod %= mod; /****/

        reverse(pos + 1, pos + 1 + len_0);
        reverse(pm + 1, pm + 1 + len_0);
        reverse(pmod + 1, pmod + 1 + len_0);
        up(i, 1, len_0) memset(dp[i], 0, sizeof(dp[i]));
        dp[0][0] = 1;

        for(ll i = 1; i <= len_0; i++)
        {
            for(ll j = 0; j <= 9; j++)
            {
                int tmp =  j * pm[i] % m; //ll tmp = tmm; tmp = (tmp + pm[i] * j % m) % m; 因为就在这一位,所以不用累计
                for(ll k = 0; k < m; k++)
                {
                    dp[i][ (tmp + k) % m ] += dp[i - 1][k]; // dp 递推
                    if(dp[i][ (tmp + k) % m ] > INF) // 过大,取个顶
                        dp[i][ (tmp + k) % m ] = INF;
                }
            }
        }

        tmm = (m - tmm + m) % m; // 剩余的需要提供的余数
        ll tmp = tmod; // 当前 ? 都为 0 时候的答案。

        while(q--)
        {
            scanf("%lld", &k);
            if(dp[len_0][tmm] < k)  /*****/
            {
                puts("-1");
                continue;
            }

            ll ans = tmp; // 取最基础的答案,就是 ? 都是0
            ll now_mod = tmm, nxt_mod;

            for(ll i = min(len_0, 30LL); i >= 1; i--) // 康拓展开的思想
            {
                for(ll j = 0; j <= 9; j++) // 一直累计,这一位满
                {
                    nxt_mod = (now_mod - pm[i] * j % m + m) % m; // 有下一位提供的余数的贡献值
                    if( dp[i - 1][nxt_mod] < k ) // 有多少个
                    {
                        k -= dp[i - 1][nxt_mod];
                    }
                    else // 个数太多,到下一位去
                    {
                        ans = (j * pmod[i] + ans) % mod;
                        now_mod = nxt_mod; /**** 下次需要替换 */
                        break;
                    }
                }
            }
            printf("%lld\n", ans);
        }
    }
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值